diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumListRequestTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumListRequestTest.kt index 37ee2f90..b580ad6d 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumListRequestTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAlbumListRequestTest.kt @@ -2,9 +2,9 @@ package org.moire.ultrasonic.api.subsonic import org.amshove.kluent.`should be equal to` import org.junit.Test +import org.moire.ultrasonic.api.subsonic.models.Album import org.moire.ultrasonic.api.subsonic.models.AlbumListType import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE -import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild /** * Integration tests for [SubsonicAPIDefinition] for getAlbumList call. @@ -28,8 +28,8 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() { assertResponseSuccessful(response) with(response.body()!!.albumList) { size `should be equal to` 2 - this[1] `should be equal to` MusicDirectoryChild( - id = "9997", parent = "9996", isDir = true, + this[1] `should be equal to` Album( + id = "9997", parent = "9996", title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful", artist = "Nightwish", year = 2015, genre = "Symphonic Metal", coverArt = "9997", playCount = 11, diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt index 9ab6b79b..17cb3ef0 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiSearchTwoTest.kt @@ -3,6 +3,7 @@ package org.moire.ultrasonic.api.subsonic import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should not be` import org.junit.Test +import org.moire.ultrasonic.api.subsonic.models.Album import org.moire.ultrasonic.api.subsonic.models.Artist import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult @@ -32,9 +33,8 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() { artistList.size `should be equal to` 1 artistList[0] `should be equal to` Artist(id = "522", name = "The Prodigy") albumList.size `should be equal to` 1 - albumList[0] `should be equal to` MusicDirectoryChild( - id = "8867", parent = "522", - isDir = true, title = "Always Outnumbered, Never Outgunned", + albumList[0] `should be equal to` Album( + id = "8867", parent = "522", title = "Always Outnumbered, Never Outgunned", album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy", year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0, created = parseDate("2016-10-23T20:57:27.000Z") diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Album.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Album.kt index 47ad7cd8..f326caae 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Album.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Album.kt @@ -18,6 +18,7 @@ data class Album( val duration: Int = 0, val year: Int = 0, val genre: String = "", + val playCount: Int = 0, @JsonProperty("song") val songList: List = emptyList(), @JsonProperty("starred") val starredDate: String = "" ) diff --git a/detekt-config.yml b/detekt-config.yml index 8729b6e3..2bb90612 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -42,8 +42,8 @@ empty-blocks: complexity: active: true TooManyFunctions: - thresholdInFiles: 20 - thresholdInClasses: 20 + thresholdInFiles: 25 + thresholdInClasses: 25 thresholdInInterfaces: 20 thresholdInObjects: 30 LabeledExpression: diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 46313ca4..82d40231 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -1,20 +1,6 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -1086,17 +1105,6 @@ column="14"/> - - - - @@ -1502,17 +1510,6 @@ column="10"/> - - - - : MultiTypeAdapter() { fun notifyChanged() { // When the download state of an entry was changed by an external process, // increase the revision counter in order to update the UI - selectionRevision.postValue(selectionRevision.value!! + 1) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/DividerBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/DividerBinder.kt index 6e862163..eae411dd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/DividerBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/DividerBinder.kt @@ -16,7 +16,7 @@ class DividerBinder : ItemViewBinder - onContextMenuClick?.invoke(menuItem, downloadFile) + onContextMenuClick.invoke(menuItem, downloadFile) } } else { // Minimize or maximize the Text view (if song title is very long) @@ -78,22 +81,22 @@ class TrackViewBinder( } holder.itemView.setOnClickListener { - if (!checkable) { - onItemClick(downloadFile) - } else { + if (checkable && !downloadFile.song.isVideo) { val nowChecked = !holder.check.isChecked holder.isChecked = nowChecked + } else { + onItemClick(downloadFile) } } // Notify the adapter of selection changes holder.observableChecked.observe( lifecycleOwner, - { newValue -> - if (newValue) { - diffAdapter.notifySelected(item.longId) + { isCheckedNow -> + if (isCheckedNow) { + diffAdapter.notifySelected(holder.entry!!.longId) } else { - diffAdapter.notifyUnselected(item.longId) + diffAdapter.notifyUnselected(holder.entry!!.longId) } } ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewHolder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewHolder.kt index a0e98132..fc5ba282 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewHolder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewHolder.kt @@ -28,13 +28,11 @@ import timber.log.Timber /** * Used to display songs and videos in a `ListView`. - * FIXME: Add video List item - * FIXME: CHECKED bug */ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable, KoinComponent { var check: CheckedTextView = view.findViewById(R.id.song_check) - var rating: LinearLayout = view.findViewById(R.id.song_rating) + private var rating: LinearLayout = view.findViewById(R.id.song_rating) private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1) private var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2) private var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3) @@ -90,7 +88,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable } check.isVisible = (checkable && !song.isVideo) - setCheckedSilent(isSelected) + initChecked(isSelected) drag.isVisible = draggable if (ActiveServerProvider.isOffline()) { @@ -109,6 +107,11 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable setSingleStar(entry!!.starred) } + if (song.isVideo) { + artist.isVisible = false + progress.isVisible = false + } + RxBus.playerStateObservable.subscribe { setPlayIcon(it.track == downloadFile) } @@ -248,14 +251,23 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable } } - private fun setCheckedSilent(newStatus: Boolean) { + /* + * Set the checked value and re-init the MutableLiveData. + * If we would post a new value, there might be a short glitch where the track is shown with its + * old selection status before the posted value has been processed. + */ + private fun initChecked(newStatus: Boolean) { + observableChecked = MutableLiveData(newStatus) check.isChecked = newStatus } + /* + * To be correct, this method doesn't directly set the checked status. + * It only notifies the observable. If the selection tracker accepts the selection + * (might be false for Singular SelectionTrackers) then it will cause the actual modification. + */ override fun setChecked(newStatus: Boolean) { observableChecked.postValue(newStatus) - // FIXME, check if working - // check.isChecked = newStatus } override fun isChecked(): Boolean { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt index f11ab127..342e6a87 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumListFragment.kt @@ -43,10 +43,10 @@ class AlbumListFragment : EntryListFragment() { ): LiveData> { if (args == null) throw IllegalArgumentException("Required arguments are missing") - val refresh = args.getBoolean(Constants.INTENT_REFRESH) || refresh + val refresh2 = args.getBoolean(Constants.INTENT_REFRESH) || refresh val append = args.getBoolean(Constants.INTENT_APPEND) - return listModel.getAlbumList(refresh or append, refreshListView!!, args) + return listModel.getAlbumList(refresh2 or append, refreshListView!!, args) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt index 10090b65..2446e05a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt @@ -33,8 +33,8 @@ class ArtistListFragment : EntryListFragment() { * The central function to pass a query to the model and return a LiveData object */ override fun getLiveData(args: Bundle?, refresh: Boolean): LiveData> { - val refresh = args?.getBoolean(Constants.INTENT_REFRESH) ?: false || refresh - return listModel.getItems(refresh, refreshListView!!) + val refresh2 = args?.getBoolean(Constants.INTENT_REFRESH) ?: false || refresh + return listModel.getItems(refresh2, refreshListView!!) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/BookmarksFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/BookmarksFragment.kt index 8f575ce7..c9c8b6b9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/BookmarksFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/BookmarksFragment.kt @@ -34,7 +34,10 @@ class BookmarksFragment : TrackCollectionFragment() { viewAdapter.selectionType = BaseAdapter.SelectionType.SINGLE } - override fun getLiveData(args: Bundle?, refresh: Boolean): LiveData> { + override fun getLiveData( + args: Bundle?, + refresh: Boolean + ): LiveData> { listModel.viewModelScope.launch(handler) { refreshListView?.isRefreshing = true listModel.getBookmarks() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index 37451286..86a62493 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -868,7 +868,8 @@ class PlayerFragment : ) dragTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( - ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0 + ItemTouchHelper.UP or ItemTouchHelper.DOWN, + ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT ) { override fun onMove( @@ -887,7 +888,20 @@ class PlayerFragment : return true } + // Swipe to delete from playlist override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val pos = viewHolder.bindingAdapterPosition + val file = mediaPlayerController.playList[pos] + mediaPlayerController.removeFromPlaylist(file) + + val songRemoved = String.format( + resources.getString(R.string.download_song_removed), + file.song.title + ) + Util.toast(context, songRemoved) + + viewAdapter.submitList(mediaPlayerController.playList) + viewAdapter.notifyDataSetChanged() } } ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt index 073742e1..cd6b13c6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt @@ -120,7 +120,7 @@ class Downloader( } @Synchronized - @Suppress("ComplexMethod") + @Suppress("ComplexMethod", "ComplexCondition") fun checkDownloadsInternal() { if ( !Util.isExternalStoragePresent() || @@ -502,7 +502,7 @@ class Downloader( /** * Extension function - * Gathers the donwload file for a given song, and modifies shouldSave if provided. + * Gathers the download file for a given song, and modifies shouldSave if provided. */ fun MusicDirectory.Entry.getDownloadFile(save: Boolean? = null): DownloadFile { return getDownloadFileForSong(this).apply { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt index 8859a272..55bffc4c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -299,6 +299,7 @@ class MediaPlayerController( } @Synchronized + // TODO: If a playlist contains an item twice, this call will wrongly remove all fun removeFromPlaylist(downloadFile: DownloadFile) { if (downloadFile == localMediaPlayer.currentPlaying) { reset() diff --git a/ultrasonic/src/main/res/layout/list_item_track_details.xml b/ultrasonic/src/main/res/layout/list_item_track_details.xml index 2b592b0a..a224ab15 100644 --- a/ultrasonic/src/main/res/layout/list_item_track_details.xml +++ b/ultrasonic/src/main/res/layout/list_item_track_details.xml @@ -1,74 +1,84 @@ - + a:layout_weight="1"> - + a:paddingEnd="6dip" + a:textAppearance="?android:attr/textAppearanceMedium" + app:layout_constraintBottom_toTopOf="@+id/song_artist" + app:layout_constraintEnd_toStartOf="@+id/song_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Track" /> - - - - - - - - + a:drawablePadding="4dip" + a:ellipsize="end" + a:paddingEnd="4dip" + a:singleLine="true" + a:textAppearance="?android:attr/textAppearanceMedium" + app:layout_constraintBottom_toTopOf="@+id/song_artist" + app:layout_constraintEnd_toStartOf="@+id/song_duration" + app:layout_constraintStart_toEndOf="@+id/song_track" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Title" /> - + - - - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/video_details.xml b/ultrasonic/src/main/res/layout/video_details.xml deleted file mode 100644 index 2ea9473c..00000000 --- a/ultrasonic/src/main/res/layout/video_details.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/video_list_item.xml b/ultrasonic/src/main/res/layout/video_list_item.xml deleted file mode 100644 index 5851bbca..00000000 --- a/ultrasonic/src/main/res/layout/video_list_item.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt index 02159012..ff3b30f8 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/domain/APISearchConverterTest.kt @@ -42,7 +42,7 @@ class APISearchConverterTest { fun `Should convert SearchTwoResult to domain entity`() { val entity = SearchTwoResult( listOf(Artist(id = "82", name = "great-artist-name")), - listOf(MusicDirectoryChild(id = "762", artist = "bzz")), + listOf(Album(id = "762", artist = "bzz")), listOf(MusicDirectoryChild(id = "9118", parent = "112")) )