From 9c07ad2d34780041596c7b25dbd473a502458e8e Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 19 Mar 2022 15:41:03 -0600 Subject: [PATCH] sort: completely rework class Completely refactor the Sort class to be more efficient and straightforward. The original Sort class had *major* shortcomings, it was slow, poorly organized, and relied on abusing compareBy to implement special things like article sort. This rework eliminates all of that in favor of a new system relying on custom comparators and chaining to achieve something much faster and maintainable. --- .../main/java/org/oxycblt/auxio/AuxioApp.kt | 3 + .../java/org/oxycblt/auxio/coil/Fetchers.kt | 2 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 8 +- .../org/oxycblt/auxio/home/HomeViewModel.kt | 16 +- .../home/fastscroll/FastScrollRecyclerView.kt | 26 +- .../org/oxycblt/auxio/music/MusicLoader.kt | 15 +- .../auxio/playback/PlaybackViewModel.kt | 4 +- .../playback/state/PlaybackStateManager.kt | 8 +- .../oxycblt/auxio/search/SearchViewModel.kt | 8 +- .../main/java/org/oxycblt/auxio/ui/Sort.kt | 238 +++++++++++------- app/src/main/res/values/dimens.xml | 2 +- 11 files changed, 201 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt index e69342276..09fabdcfd 100644 --- a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt @@ -36,6 +36,9 @@ import org.oxycblt.auxio.settings.SettingsManager * - Rework RecyclerView management and item dragging * - Rework sealed classes to minimize whens and maximize overrides * ``` + * + * TODO: Dumpster int-codes for a 4-byte identifier (can still be in the form of an int) For + * example, instead of 0xA111 for ReplayGainMode.TRACK, you would instead have RTCK */ @Suppress("UNUSED") class AuxioApp : Application(), ImageLoaderFactory { diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt index 387ac275c..f7e7f48ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt @@ -74,7 +74,7 @@ private constructor( private val artist: Artist, ) : BaseFetcher() { override suspend fun fetch(): FetchResult? { - val albums = Sort.ByName(true).sortAlbums(artist.albums) + val albums = Sort.ByName(true).albums(artist.albums) val results = albums.mapAtMost(4) { album -> fetchArt(context, album) } return createMosaic(context, results, size) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index f46afc5f7..33d743414 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -162,7 +162,7 @@ class DetailViewModel : ViewModel() { mShowMenu.value = MenuConfig(view, settingsManager.detailGenreSort) })) - data.addAll(settingsManager.detailGenreSort.sortGenre(curGenre.value!!)) + data.addAll(settingsManager.detailGenreSort.genre(curGenre.value!!)) mGenreData.value = data } @@ -174,7 +174,7 @@ class DetailViewModel : ViewModel() { data.add(Header(id = -2, string = R.string.lbl_albums)) - data.addAll(Sort.ByYear(false).sortAlbums(artist.albums)) + data.addAll(Sort.ByYear(false).albums(artist.albums)) data.add( ActionHeader( @@ -187,7 +187,7 @@ class DetailViewModel : ViewModel() { mShowMenu.value = MenuConfig(view, settingsManager.detailArtistSort) })) - data.addAll(settingsManager.detailArtistSort.sortArtist(artist)) + data.addAll(settingsManager.detailArtistSort.artist(artist)) mArtistData.value = data.toList() } @@ -208,7 +208,7 @@ class DetailViewModel : ViewModel() { mShowMenu.value = MenuConfig(view, settingsManager.detailAlbumSort) })) - data.addAll(settingsManager.detailAlbumSort.sortAlbum(curAlbum.value!!)) + data.addAll(settingsManager.detailAlbumSort.album(curAlbum.value!!)) mAlbumData.value = data } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index f8af2e412..ee1073b78 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -81,10 +81,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback { viewModelScope.launch { val musicStore = MusicStore.awaitInstance() - mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs) - mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums) - mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists) - mGenres.value = settingsManager.libGenreSort.sortParents(musicStore.genres) + mSongs.value = settingsManager.libSongSort.songs(musicStore.songs) + mAlbums.value = settingsManager.libAlbumSort.albums(musicStore.albums) + mArtists.value = settingsManager.libArtistSort.artists(musicStore.artists) + mGenres.value = settingsManager.libGenreSort.genres(musicStore.genres) } } @@ -113,19 +113,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback { when (mCurTab.value) { DisplayMode.SHOW_SONGS -> { settingsManager.libSongSort = sort - mSongs.value = sort.sortSongs(mSongs.value!!) + mSongs.value = sort.songs(mSongs.value!!) } DisplayMode.SHOW_ALBUMS -> { settingsManager.libAlbumSort = sort - mAlbums.value = sort.sortAlbums(mAlbums.value!!) + mAlbums.value = sort.albums(mAlbums.value!!) } DisplayMode.SHOW_ARTISTS -> { settingsManager.libArtistSort = sort - mArtists.value = sort.sortParents(mArtists.value!!) + mArtists.value = sort.artists(mArtists.value!!) } DisplayMode.SHOW_GENRES -> { settingsManager.libGenreSort = sort - mGenres.value = sort.sortParents(mGenres.value!!) + mGenres.value = sort.genres(mGenres.value!!) } else -> {} } diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index 077588a84..e73d168e0 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -69,19 +69,11 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * - Added documentation * * @author Hai Zhang, OxygenCobalt - * - * TODO: Fix strange touch behavior when the pointer is slightly outside of the view. - * - * TODO: Really try to make this view less insane. */ class FastScrollRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : EdgeRecyclerView(context, attrs, defStyleAttr) { - private val minTouchTargetSize = - context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size) - private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop - // Thumb private val thumbView = View(context).apply { @@ -102,6 +94,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr } } + private val scrollPositionChildRect = Rect() + // Popup private val popupView = FastScrollPopupView(context).apply { @@ -118,7 +112,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr private var showingPopup = false - // Touch events + // Touch + private val minTouchTargetSize = + context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size) + private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop + private var downX = 0f private var downY = 0f private var lastY = 0f @@ -151,8 +149,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr onDragListener?.invoke(value) } - private val childRect = Rect() - /** Callback to provide a string to be shown on the popup when an item is passed */ var popupProvider: ((Int) -> String)? = null @@ -303,8 +299,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr // Combine the previous item dimensions with the current item top to find our scroll // position - getDecoratedBoundsWithMargins(getChildAt(0), childRect) - val scrollOffset = paddingTop + (firstAdapterPos * itemHeight) - childRect.top + getDecoratedBoundsWithMargins(getChildAt(0), scrollPositionChildRect) + val scrollOffset = paddingTop + (firstAdapterPos * itemHeight) - scrollPositionChildRect.top // Then calculate the thumb position, which is just: // [proportion of scroll position to scroll range] * [total thumb range] @@ -497,8 +493,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr } val itemView = getChildAt(0) - getDecoratedBoundsWithMargins(itemView, childRect) - return childRect.height() + getDecoratedBoundsWithMargins(itemView, scrollPositionChildRect) + return scrollPositionChildRect.height() } private val itemCount: Int diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 2db8378cf..fd54f6136 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -27,6 +27,7 @@ import androidx.core.database.getStringOrNull import androidx.core.text.isDigitsOnly import org.oxycblt.auxio.R import org.oxycblt.auxio.music.excluded.ExcludedDatabase +import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.logD /** @@ -277,9 +278,17 @@ class MusicLoader { // Use the song with the latest year as our metadata song. // This allows us to replicate the LAST_YEAR field, which is useful as it means that // weird years like "0" wont show up if there are alternatives. - // TODO: Weigh songs with null years lower than songs with zero years - val templateSong = - requireNotNull(albumSongs.maxByOrNull { it.internalMediaStoreYear ?: 0 }) + // Note: Normally we could want to use something like maxByWith, but apparently + // that does not exist in the kotlin stdlib yet. + val comparator = Sort.NullableComparator() + var templateSong = albumSongs[0] + for (i in 1..albumSongs.lastIndex) { + val candidate = albumSongs[i] + if (comparator.compare(templateSong.track, candidate.track) < 0) { + templateSong = candidate + } + } + val albumName = templateSong.internalMediaStoreAlbumName val albumYear = templateSong.internalMediaStoreYear val albumCoverUri = diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 14f30667e..6d143a8f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -240,7 +240,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { /** Add an [Album] to the top of the queue. */ fun playNext(album: Album) { - playbackManager.playNext(settingsManager.detailAlbumSort.sortAlbum(album)) + playbackManager.playNext(settingsManager.detailAlbumSort.album(album)) } /** Add a [Song] to the end of the queue. */ @@ -250,7 +250,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { /** Add an [Album] to the end of the queue. */ fun addToQueue(album: Album) { - playbackManager.addToQueue(settingsManager.detailAlbumSort.sortAlbum(album)) + playbackManager.addToQueue(settingsManager.detailAlbumSort.album(album)) } // --- STATUS FUNCTIONS --- diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 19c583df8..feb4a86fc 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -376,13 +376,13 @@ class PlaybackStateManager private constructor() { mQueue = when (mPlaybackMode) { PlaybackMode.ALL_SONGS -> - settingsManager.libSongSort.sortSongs(musicStore.songs).toMutableList() + settingsManager.libSongSort.songs(musicStore.songs).toMutableList() PlaybackMode.IN_ALBUM -> - settingsManager.detailAlbumSort.sortAlbum(mParent as Album).toMutableList() + settingsManager.detailAlbumSort.album(mParent as Album).toMutableList() PlaybackMode.IN_ARTIST -> - settingsManager.detailArtistSort.sortArtist(mParent as Artist).toMutableList() + settingsManager.detailArtistSort.artist(mParent as Artist).toMutableList() PlaybackMode.IN_GENRE -> - settingsManager.detailGenreSort.sortGenre(mParent as Genre).toMutableList() + settingsManager.detailGenreSort.genre(mParent as Genre).toMutableList() } if (keepSong) { diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index ef47a3320..a5409ed29 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -88,28 +88,28 @@ class SearchViewModel : ViewModel() { if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ARTISTS) { musicStore.artists.filterByOrNull(query)?.let { artists -> results.add(Header(-1, R.string.lbl_artists)) - results.addAll(sort.sortParents(artists)) + results.addAll(sort.artists(artists)) } } if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ALBUMS) { musicStore.albums.filterByOrNull(query)?.let { albums -> results.add(Header(-2, R.string.lbl_albums)) - results.addAll(sort.sortAlbums(albums)) + results.addAll(sort.albums(albums)) } } if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_GENRES) { musicStore.genres.filterByOrNull(query)?.let { genres -> results.add(Header(-3, R.string.lbl_genres)) - results.addAll(sort.sortParents(genres)) + results.addAll(sort.genres(genres)) } } if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_SONGS) { musicStore.songs.filterByOrNull(query)?.let { songs -> results.add(Header(-4, R.string.lbl_songs)) - results.addAll(sort.sortSongs(songs)) + results.addAll(sort.songs(songs)) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index 7465b333f..2a6a04d4b 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -23,8 +23,8 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.logW /** * A data class representing the sort modes used in Auxio. @@ -43,14 +43,95 @@ import org.oxycblt.auxio.music.Song * @author OxygenCobalt */ sealed class Sort(open val isAscending: Boolean) { + open fun songs(songs: Collection): List { + logW("This sort is not supported for songs") + return songs.toList() + } + + open fun albums(albums: Collection): List { + logW("This sort is not supported for albums") + return albums.toList() + } + + open fun artists(artists: Collection): List { + logW("This sort is not supported for artists") + return artists.toList() + } + + open fun genres(genres: Collection): List { + logW("This sort is not supported for genres") + return genres.toList() + } + /** Sort by the names of an item */ - class ByName(override val isAscending: Boolean) : Sort(isAscending) - /** Sort by the artist of an item, only supported by [Album] and [Song] */ - class ByArtist(override val isAscending: Boolean) : Sort(isAscending) + class ByName(override val isAscending: Boolean) : Sort(isAscending) { + override fun songs(songs: Collection): List { + return songs.sortedWith(compareByDynamic(NameComparator()) { it }) + } + + override fun albums(albums: Collection): List { + return albums.sortedWith(compareByDynamic(NameComparator()) { it }) + } + + override fun artists(artists: Collection): List { + return artists.sortedWith(compareByDynamic(NameComparator()) { it }) + } + + override fun genres(genres: Collection): List { + return genres.sortedWith(compareByDynamic(NameComparator()) { it }) + } + } + /** Sort by the album of an item, only supported by [Song] */ - class ByAlbum(override val isAscending: Boolean) : Sort(isAscending) + class ByAlbum(override val isAscending: Boolean) : Sort(isAscending) { + override fun songs(songs: Collection): List { + return songs.sortedWith( + MultiComparator( + compareByDynamic(NameComparator()) { it.album }, + compareBy(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) + } + } + + /** Sort by the artist of an item, only supported by [Album] and [Song] */ + class ByArtist(override val isAscending: Boolean) : Sort(isAscending) { + override fun songs(songs: Collection): List { + return songs.sortedWith( + MultiComparator( + compareByDynamic(NameComparator()) { it.album.artist }, + compareByDescending(NullableComparator()) { it.album.year }, + compareByDescending(NameComparator()) { it.album }, + compareBy(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) + } + + override fun albums(albums: Collection): List { + return albums.sortedWith( + MultiComparator( + compareByDynamic(NameComparator()) { it.artist }, + compareByDescending(NullableComparator()) { it.year }, + compareBy(NameComparator()) { it })) + } + } + /** Sort by the year of an item, only supported by [Album] and [Song] */ - class ByYear(override val isAscending: Boolean) : Sort(isAscending) + class ByYear(override val isAscending: Boolean) : Sort(isAscending) { + override fun songs(songs: Collection): List { + return songs.sortedWith( + MultiComparator( + compareByDynamic(NullableComparator()) { it.album.year }, + compareByDescending(NameComparator()) { it.album }, + compareBy(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) + } + + override fun albums(albums: Collection): List { + return albums.sortedWith( + MultiComparator( + compareByDynamic(NullableComparator()) { it.year }, + compareBy(NameComparator()) { it })) + } + } /** Get the corresponding item id for this sort. */ val itemId: Int @@ -89,72 +170,31 @@ sealed class Sort(open val isAscending: Boolean) { } } - /** - * Sort a list of [Song] instances to reflect this specific sort. - * - * Albums are sorted by ascending track, artists are sorted with [ByYear] descending. - * - * @return A sorted list of songs - */ - fun sortSongs(songs: Collection): List { - return when (this) { - is ByName -> songs.stringSort { it.resolvedName } - else -> - sortAlbums(songs.groupBy { it.album }.keys).flatMap { album -> - album.songs.intSort(true) { it.track ?: 0 } - } - } - } - - /** - * Sort a list of [Album] instances to reflect this specific sort. - * - * Artists are sorted with [ByYear] descending. - * - * @return A sorted list of albums - */ - fun sortAlbums(albums: Collection): List { - return when (this) { - is ByName, is ByAlbum -> albums.stringSort { it.resolvedName } - is ByArtist -> - sortParents(albums.groupBy { it.artist }.keys).flatMap { - ByYear(false).sortAlbums(it.albums) - } - is ByYear -> albums.intSort { it.year ?: 0 } - } - } - - /** - * Sort a list of [MusicParent] instances to reflect this specific sort. - * - * @return A sorted list of the specific parent - */ - fun sortParents(parents: Collection): List { - return parents.stringSort { it.resolvedName } - } - /** * Sort the songs in an album. - * @see sortSongs + * @see songs */ - fun sortAlbum(album: Album): List { - return album.songs.intSort { it.track ?: 0 } + fun album(album: Album): List { + return album.songs.sortedWith( + MultiComparator( + compareByDynamic(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) } /** * Sort the songs in an artist. - * @see sortSongs + * @see songs */ - fun sortArtist(artist: Artist): List { - return sortSongs(artist.songs) + fun artist(artist: Artist): List { + return songs(artist.songs) } /** * Sort the songs in a genre. - * @see sortSongs + * @see songs */ - fun sortGenre(genre: Genre): List { - return sortSongs(genre.songs) + fun genre(genre: Genre): List { + return songs(genre.songs) } /** Convert this sort to it's integer representation. */ @@ -167,35 +207,59 @@ sealed class Sort(open val isAscending: Boolean) { }.shl(1) or if (isAscending) 1 else 0 } - private fun Collection.stringSort( - asc: Boolean = isAscending, - selector: (T) -> String - ): List { - // Chain whatever item call with sliceArticle for correctness - val chained: (T) -> String = { selector(it).sliceArticle() } - - val comparator = - if (asc) { - compareBy(String.CASE_INSENSITIVE_ORDER, chained) - } else { - compareByDescending(String.CASE_INSENSITIVE_ORDER, chained) - } - - return sortedWith(comparator) + protected inline fun compareByDynamic( + comparator: Comparator, + crossinline selector: (T) -> K + ): Comparator { + return if (isAscending) { + compareBy(comparator, selector) + } else { + compareByDescending(comparator, selector) + } } - private fun Collection.intSort( - asc: Boolean = isAscending, - selector: (T) -> Int, - ): List { - val comparator = - if (asc) { - compareBy(selector) - } else { - compareByDescending(selector) + class NameComparator : Comparator { + override fun compare(a: T?, b: T?): Int { + if (a == null && b != null) return -1 // -1 -> a < b + if (a == null && b == null) return 0 // 0 -> 0 = b + if (a != null && b == null) return 1 // 1 -> a > b + + return a!!.resolvedName + .sliceArticle() + .compareTo(b!!.resolvedName.sliceArticle(), ignoreCase = true) + } + } + + class NullableComparator> : Comparator { + override fun compare(a: T?, b: T?): Int { + if (a == null && b != null) return -1 // -1 -> a < b + if (a == null && b == null) return 0 // 0 -> 0 = b + if (a != null && b == null) return 1 // 1 -> a > b + return a!!.compareTo(b!!) + } + } + + /** + * Chains the given comparators together to form one comparator. + * + * Sorts often need to compare multiple things at once across several hierarchies, with this + * class doing such in a more efficient manner than resorting at multiple intervals or grouping + * items up. Comparators are checked from first to last, with the first comparator that returns a + * non-equal result being propagated upwards. + */ + class MultiComparator(vararg comparators: Comparator) : Comparator { + private val mComparators = comparators + + override fun compare(a: T?, b: T?): Int { + for (comparator in mComparators) { + val result = comparator.compare(a, b) + if (result != 0) { + return result + } } - return sortedWith(comparator) + return 0 + } } companion object { @@ -229,15 +293,15 @@ sealed class Sort(open val isAscending: Boolean) { * languages. */ fun String.sliceArticle(): String { - if (length > 5 && startsWith("the ", true)) { + if (length > 5 && startsWith("the ", ignoreCase = true)) { return slice(4..lastIndex) } - if (length > 4 && startsWith("an ", true)) { + if (length > 4 && startsWith("an ", ignoreCase = true)) { return slice(3..lastIndex) } - if (length > 3 && startsWith("a ", true)) { + if (length > 3 && startsWith("a ", ignoreCase = true)) { return slice(2..lastIndex) } diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f6d0f55dd..70e39884a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -30,7 +30,7 @@ 2dp 4dp - 80dp + 78dp 64dp @dimen/spacing_medium 28dp