Merge pull request #604 from ultrasonic/downloader-ld
Implement a Download view
This commit is contained in:
commit
7d2923230c
|
@ -11,9 +11,9 @@ data class Artist(
|
||||||
override var coverArt: String? = null,
|
override var coverArt: String? = null,
|
||||||
override var albumCount: Long? = null,
|
override var albumCount: Long? = null,
|
||||||
override var closeness: Int = 0
|
override var closeness: Int = 0
|
||||||
) : ArtistOrIndex(id), Comparable<Artist> {
|
) : ArtistOrIndex(id) {
|
||||||
|
|
||||||
override fun compareTo(other: Artist): Int {
|
fun compareTo(other: Artist): Int {
|
||||||
when {
|
when {
|
||||||
this.closeness == other.closeness -> {
|
this.closeness == other.closeness -> {
|
||||||
return 0
|
return 0
|
||||||
|
@ -26,4 +26,6 @@ data class Artist(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: Identifiable) = compareTo(other as Artist)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
|
|
||||||
open class ArtistOrIndex(
|
abstract class ArtistOrIndex(
|
||||||
@Ignore
|
@Ignore
|
||||||
override var id: String,
|
override var id: String,
|
||||||
@Ignore
|
@Ignore
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package org.moire.ultrasonic.domain
|
|
||||||
|
|
||||||
import androidx.room.Ignore
|
|
||||||
|
|
||||||
open class GenericEntry {
|
|
||||||
// TODO Should be non-null!
|
|
||||||
@Ignore
|
|
||||||
open val id: String? = null
|
|
||||||
@Ignore
|
|
||||||
open val name: String? = null
|
|
||||||
|
|
||||||
// These are just a formality and will never be called,
|
|
||||||
// because Kotlin data classes will have autogenerated equals() and hashCode() functions
|
|
||||||
override operator fun equals(other: Any?): Boolean {
|
|
||||||
return this === other
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id?.hashCode() ?: 0
|
|
||||||
result = 31 * result + (name?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,8 +7,8 @@ import java.io.Serializable
|
||||||
@Entity
|
@Entity
|
||||||
data class Genre(
|
data class Genre(
|
||||||
@PrimaryKey val index: String,
|
@PrimaryKey val index: String,
|
||||||
override val name: String
|
val name: String
|
||||||
) : Serializable, GenericEntry() {
|
) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val serialVersionUID = -3943025175219134028L
|
private const val serialVersionUID = -3943025175219134028L
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.Ignore
|
||||||
|
|
||||||
|
abstract class GenericEntry : Identifiable {
|
||||||
|
abstract override val id: String
|
||||||
|
@Ignore
|
||||||
|
open val name: String? = null
|
||||||
|
override fun compareTo(other: Identifiable): Int {
|
||||||
|
return this.id.toInt().compareTo(other.id.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Identifiable : Comparable<Identifiable> {
|
||||||
|
val id: String
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ class MusicDirectory {
|
||||||
var bookmarkPosition: Int = 0,
|
var bookmarkPosition: Int = 0,
|
||||||
var userRating: Int? = null,
|
var userRating: Int? = null,
|
||||||
var averageRating: Float? = null
|
var averageRating: Float? = null
|
||||||
) : Serializable, GenericEntry(), Comparable<Entry> {
|
) : Serializable, GenericEntry() {
|
||||||
fun setDuration(duration: Long) {
|
fun setDuration(duration: Long) {
|
||||||
this.duration = duration.toInt()
|
this.duration = duration.toInt()
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class MusicDirectory {
|
||||||
private const val serialVersionUID = -3339106650010798108L
|
private const val serialVersionUID = -3339106650010798108L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: Entry): Int {
|
fun compareTo(other: Entry): Int {
|
||||||
when {
|
when {
|
||||||
this.closeness == other.closeness -> {
|
this.closeness == other.closeness -> {
|
||||||
return 0
|
return 0
|
||||||
|
@ -91,5 +91,7 @@ class MusicDirectory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: Identifiable) = compareTo(other as Entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import java.io.Serializable
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
||||||
|
|
||||||
data class Share(
|
data class Share(
|
||||||
override var id: String? = null,
|
override var id: String,
|
||||||
var url: String? = null,
|
var url: String? = null,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
var username: String? = null,
|
var username: String? = null,
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="128"
|
line="132"
|
||||||
column="5"/>
|
column="5"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="145"
|
line="149"
|
||||||
column="5"/>
|
column="5"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="146"
|
line="150"
|
||||||
column="5"/>
|
column="5"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="109"
|
line="112"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -393,7 +393,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="109"
|
line="112"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -409,7 +409,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="109"
|
line="112"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -429,7 +429,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="80"
|
line="108"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="122"
|
line="126"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -452,7 +452,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="122"
|
line="125"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -468,7 +468,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="120"
|
line="125"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -488,7 +488,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="90"
|
line="121"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="127"
|
line="131"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -511,7 +511,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="127"
|
line="130"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -527,7 +527,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="125"
|
line="130"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -547,7 +547,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="95"
|
line="126"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -558,7 +558,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="128"
|
line="132"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -570,7 +570,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="128"
|
line="131"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -586,7 +586,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="126"
|
line="131"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -604,6 +604,10 @@
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
line="128"
|
line="128"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
|
line="127"
|
||||||
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
|
@ -613,7 +617,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="129"
|
line="133"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -625,7 +629,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="129"
|
line="132"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -641,7 +645,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="127"
|
line="132"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -661,7 +665,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="96"
|
line="128"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -672,7 +676,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="130"
|
line="134"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -684,7 +688,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="130"
|
line="133"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -700,7 +704,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="128"
|
line="133"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -720,7 +724,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="97"
|
line="129"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -731,7 +735,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="135"
|
line="139"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -743,7 +747,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="135"
|
line="138"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -759,7 +763,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="133"
|
line="138"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -779,7 +783,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="100"
|
line="134"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -790,7 +794,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="141"
|
line="145"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -802,7 +806,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="141"
|
line="144"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -818,7 +822,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="139"
|
line="144"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -838,7 +842,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="106"
|
line="140"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -849,7 +853,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="154"
|
line="158"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -861,7 +865,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="154"
|
line="157"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -877,7 +881,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="152"
|
line="157"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -895,6 +899,10 @@
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
line="154"
|
line="154"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
|
line="153"
|
||||||
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
|
@ -904,7 +912,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="155"
|
line="159"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -915,7 +923,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="156"
|
line="160"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -926,7 +934,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="172"
|
line="176"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -938,7 +946,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="170"
|
line="173"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -954,7 +962,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="168"
|
line="173"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -974,7 +982,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="131"
|
line="169"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -985,7 +993,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="224"
|
line="228"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -997,7 +1005,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="222"
|
line="225"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -1013,7 +1021,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="220"
|
line="225"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -1031,6 +1039,10 @@
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
line="222"
|
line="222"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
|
line="219"
|
||||||
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
|
@ -1040,7 +1052,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="293"
|
line="297"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -1052,7 +1064,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="289"
|
line="294"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -1068,7 +1080,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="287"
|
line="294"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -1080,7 +1092,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pt-rBR/strings.xml"
|
file="src/main/res/values-pt-rBR/strings.xml"
|
||||||
line="289"
|
line="291"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
|
@ -1088,7 +1100,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-zh-rCN/strings.xml"
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
line="213"
|
line="287"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -1099,7 +1111,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="296"
|
line="300"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
|
@ -1111,7 +1123,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="292"
|
line="297"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
|
@ -1127,7 +1139,7 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="290"
|
line="297"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
|
@ -1139,12 +1151,16 @@
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pt-rBR/strings.xml"
|
file="src/main/res/values-pt-rBR/strings.xml"
|
||||||
line="292"
|
line="294"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
line="292"
|
line="292"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
|
line="290"
|
||||||
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
|
@ -1154,7 +1170,7 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="390"
|
line="397"
|
||||||
column="13"/>
|
column="13"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -1165,47 +1181,47 @@
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values/strings.xml"
|
file="src/main/res/values/strings.xml"
|
||||||
line="469"
|
line="476"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-cs/strings.xml"
|
file="src/main/res/values-cs/strings.xml"
|
||||||
line="450"
|
line="450"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
|
||||||
file="src/main/res/values-de/strings.xml"
|
|
||||||
line="391"
|
|
||||||
column="14"/>
|
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-es/strings.xml"
|
file="src/main/res/values-es/strings.xml"
|
||||||
line="463"
|
line="472"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-fr/strings.xml"
|
file="src/main/res/values-fr/strings.xml"
|
||||||
line="463"
|
line="461"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-hu/strings.xml"
|
file="src/main/res/values-hu/strings.xml"
|
||||||
line="458"
|
line="456"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-nl/strings.xml"
|
file="src/main/res/values-nl/strings.xml"
|
||||||
line="460"
|
line="472"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pl/strings.xml"
|
file="src/main/res/values-pl/strings.xml"
|
||||||
line="404"
|
line="403"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pt/strings.xml"
|
file="src/main/res/values-pt/strings.xml"
|
||||||
line="391"
|
line="390"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-pt-rBR/strings.xml"
|
file="src/main/res/values-pt-rBR/strings.xml"
|
||||||
line="463"
|
line="465"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
<location
|
<location
|
||||||
file="src/main/res/values-ru/strings.xml"
|
file="src/main/res/values-ru/strings.xml"
|
||||||
line="475"
|
line="473"
|
||||||
|
column="14"/>
|
||||||
|
<location
|
||||||
|
file="src/main/res/values-zh-rCN/strings.xml"
|
||||||
|
line="454"
|
||||||
column="14"/>
|
column="14"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -1242,94 +1258,6 @@
|
||||||
column="6"/>
|
column="6"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-it/strings.xml"
|
|
||||||
line="312"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-de/strings.xml"
|
|
||||||
line="323"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-pl/strings.xml"
|
|
||||||
line="323"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-cs/strings.xml"
|
|
||||||
line="326"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-hu/strings.xml"
|
|
||||||
line="340"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyDashes"
|
|
||||||
message="Replace "--" with an "em dash" character (—, &#8212;) ?"
|
|
||||||
errorLine1=" <string name="util.no_time">-:--</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-nl/strings.xml"
|
|
||||||
line="340"
|
|
||||||
column="33"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyEllipsis"
|
|
||||||
message="Replace "..." with ellipsis character (…, &#8230;) ?"
|
|
||||||
errorLine1=" <string name="parser.reading">Bezig met uitlezen van server...</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-nl/strings.xml"
|
|
||||||
line="127"
|
|
||||||
column="35"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="TypographyEllipsis"
|
|
||||||
message="Replace "..." with ellipsis character (…, &#8230;) ?"
|
|
||||||
errorLine1=" <string name="service.connecting">Bezig met verbinden met server; even geduld...</string>"
|
|
||||||
errorLine2=" ^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values-nl/strings.xml"
|
|
||||||
line="152"
|
|
||||||
column="39"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="IconDuplicates"
|
id="IconDuplicates"
|
||||||
message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png">
|
message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png">
|
||||||
|
@ -1368,7 +1296,7 @@
|
||||||
errorLine2=" ~~~~~~~~">
|
errorLine2=" ~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/layout/share_details.xml"
|
file="src/main/res/layout/share_details.xml"
|
||||||
line="22"
|
line="30"
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
@ -2138,7 +2066,7 @@
|
||||||
errorLine2=" ~~~~~~~~">
|
errorLine2=" ~~~~~~~~">
|
||||||
<location
|
<location
|
||||||
file="src/main/res/layout/share_details.xml"
|
file="src/main/res/layout/share_details.xml"
|
||||||
line="22"
|
line="30"
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ public class JukeboxMediaPlayer
|
||||||
tasks.remove(Start.class);
|
tasks.remove(Start.class);
|
||||||
|
|
||||||
List<String> ids = new ArrayList<>();
|
List<String> ids = new ArrayList<>();
|
||||||
for (DownloadFile file : downloader.getDownloads())
|
for (DownloadFile file : downloader.getAll())
|
||||||
{
|
{
|
||||||
ids.add(file.getSong().getId());
|
ids.add(file.getSong().getId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,3 +8,5 @@ public abstract class Supplier<T>
|
||||||
{
|
{
|
||||||
public abstract T get();
|
public abstract T get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,7 @@ public class CacheCleaner
|
||||||
|
|
||||||
Lazy<Downloader> downloader = inject(Downloader.class);
|
Lazy<Downloader> downloader = inject(Downloader.class);
|
||||||
|
|
||||||
for (DownloadFile downloadFile : downloader.getValue().getDownloads())
|
for (DownloadFile downloadFile : downloader.getValue().getAll())
|
||||||
{
|
{
|
||||||
filesToNotDelete.add(downloadFile.getPartialFile());
|
filesToNotDelete.add(downloadFile.getPartialFile());
|
||||||
filesToNotDelete.add(downloadFile.getCompleteOrSaveFile());
|
filesToNotDelete.add(downloadFile.getCompleteOrSaveFile());
|
||||||
|
|
|
@ -108,6 +108,7 @@ class NavigationActivity : AppCompatActivity() {
|
||||||
R.id.mediaLibraryFragment,
|
R.id.mediaLibraryFragment,
|
||||||
R.id.searchFragment,
|
R.id.searchFragment,
|
||||||
R.id.playlistsFragment,
|
R.id.playlistsFragment,
|
||||||
|
R.id.downloadsFragment,
|
||||||
R.id.sharesFragment,
|
R.id.sharesFragment,
|
||||||
R.id.bookmarksFragment,
|
R.id.bookmarksFragment,
|
||||||
R.id.chatFragment,
|
R.id.chatFragment,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import org.moire.ultrasonic.util.Constants
|
||||||
* Displays a list of Albums from the media library
|
* Displays a list of Albums from the media library
|
||||||
* TODO: Check refresh is working
|
* TODO: Check refresh is working
|
||||||
*/
|
*/
|
||||||
class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdapter>() {
|
class AlbumListFragment : EntryListFragment<MusicDirectory.Entry, AlbumRowAdapter>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel to use to get the data
|
* The ViewModel to use to get the data
|
||||||
|
|
|
@ -28,7 +28,7 @@ import timber.log.Timber
|
||||||
* Creates a Row in a RecyclerView which contains the details of an Album
|
* Creates a Row in a RecyclerView which contains the details of an Album
|
||||||
*/
|
*/
|
||||||
class AlbumRowAdapter(
|
class AlbumRowAdapter(
|
||||||
albumList: List<MusicDirectory.Entry>,
|
itemList: List<MusicDirectory.Entry>,
|
||||||
onItemClick: (MusicDirectory.Entry) -> Unit,
|
onItemClick: (MusicDirectory.Entry) -> Unit,
|
||||||
onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
|
@ -40,27 +40,23 @@ class AlbumRowAdapter(
|
||||||
onMusicFolderUpdate
|
onMusicFolderUpdate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
super.submitList(itemList)
|
||||||
|
}
|
||||||
|
|
||||||
private val starDrawable: Drawable =
|
private val starDrawable: Drawable =
|
||||||
Util.getDrawableFromAttribute(context, R.attr.star_full)
|
Util.getDrawableFromAttribute(context, R.attr.star_full)
|
||||||
private val starHollowDrawable: Drawable =
|
private val starHollowDrawable: Drawable =
|
||||||
Util.getDrawableFromAttribute(context, R.attr.star_hollow)
|
Util.getDrawableFromAttribute(context, R.attr.star_hollow)
|
||||||
|
|
||||||
override var itemList = albumList
|
|
||||||
|
|
||||||
// Set our layout files
|
// Set our layout files
|
||||||
override val layout = R.layout.album_list_item
|
override val layout = R.layout.album_list_item
|
||||||
override val contextMenuLayout = R.menu.artist_context_menu
|
override val contextMenuLayout = R.menu.artist_context_menu
|
||||||
|
|
||||||
// Sets the data to be displayed in the RecyclerView
|
|
||||||
override fun setData(data: List<MusicDirectory.Entry>) {
|
|
||||||
itemList = data
|
|
||||||
super.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is ViewHolder) {
|
if (holder is ViewHolder) {
|
||||||
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
||||||
val entry = itemList[listPosition]
|
val entry = currentList[listPosition]
|
||||||
holder.album.text = entry.title
|
holder.album.text = entry.title
|
||||||
holder.artist.text = entry.artist
|
holder.artist.text = entry.artist
|
||||||
holder.details.setOnClickListener { onItemClick(entry) }
|
holder.details.setOnClickListener { onItemClick(entry) }
|
||||||
|
@ -78,9 +74,9 @@ class AlbumRowAdapter(
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
if (selectFolderHeader != null)
|
if (selectFolderHeader != null)
|
||||||
return itemList.size + 1
|
return currentList.size + 1
|
||||||
else
|
else
|
||||||
return itemList.size
|
return currentList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.moire.ultrasonic.util.Constants
|
||||||
/**
|
/**
|
||||||
* Displays the list of Artists from the media library
|
* Displays the list of Artists from the media library
|
||||||
*/
|
*/
|
||||||
class ArtistListFragment : GenericListFragment<ArtistOrIndex, ArtistRowAdapter>() {
|
class ArtistListFragment : EntryListFragment<ArtistOrIndex, ArtistRowAdapter>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel to use to get the data
|
* The ViewModel to use to get the data
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.os.Bundle
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import java.text.Collator
|
||||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||||
import org.moire.ultrasonic.service.MusicService
|
import org.moire.ultrasonic.service.MusicService
|
||||||
|
|
||||||
|
@ -63,6 +64,11 @@ class ArtistListModel(application: Application) : GenericListModel(application)
|
||||||
result = musicService.getIndexes(musicFolderId, refresh)
|
result = musicService.getIndexes(musicFolderId, refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
artists.postValue(result.toMutableList())
|
artists.postValue(result.toMutableList().sortedWith(comparator))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val comparator: Comparator<ArtistOrIndex> =
|
||||||
|
compareBy(Collator.getInstance()) { t -> t.name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter
|
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter
|
||||||
import java.text.Collator
|
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||||
|
@ -22,7 +21,7 @@ import org.moire.ultrasonic.util.Settings
|
||||||
* Creates a Row in a RecyclerView which contains the details of an Artist
|
* Creates a Row in a RecyclerView which contains the details of an Artist
|
||||||
*/
|
*/
|
||||||
class ArtistRowAdapter(
|
class ArtistRowAdapter(
|
||||||
artistList: List<ArtistOrIndex>,
|
itemList: List<ArtistOrIndex>,
|
||||||
onItemClick: (ArtistOrIndex) -> Unit,
|
onItemClick: (ArtistOrIndex) -> Unit,
|
||||||
onContextMenuClick: (MenuItem, ArtistOrIndex) -> Boolean,
|
onContextMenuClick: (MenuItem, ArtistOrIndex) -> Boolean,
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
|
@ -34,32 +33,26 @@ class ArtistRowAdapter(
|
||||||
),
|
),
|
||||||
SectionedAdapter {
|
SectionedAdapter {
|
||||||
|
|
||||||
override var itemList = artistList
|
init {
|
||||||
|
super.submitList(itemList)
|
||||||
|
}
|
||||||
|
|
||||||
// Set our layout files
|
// Set our layout files
|
||||||
override val layout = R.layout.artist_list_item
|
override val layout = R.layout.artist_list_item
|
||||||
override val contextMenuLayout = R.menu.artist_context_menu
|
override val contextMenuLayout = R.menu.artist_context_menu
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the data to be displayed in the RecyclerView
|
|
||||||
*/
|
|
||||||
override fun setData(data: List<ArtistOrIndex>) {
|
|
||||||
itemList = data.sortedWith(compareBy(Collator.getInstance()) { t -> t.name })
|
|
||||||
super.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is ViewHolder) {
|
if (holder is ViewHolder) {
|
||||||
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
val listPosition = if (selectFolderHeader != null) position - 1 else position
|
||||||
holder.textView.text = itemList[listPosition].name
|
holder.textView.text = currentList[listPosition].name
|
||||||
holder.section.text = getSectionForArtist(listPosition)
|
holder.section.text = getSectionForArtist(listPosition)
|
||||||
holder.layout.setOnClickListener { onItemClick(itemList[listPosition]) }
|
holder.layout.setOnClickListener { onItemClick(currentList[listPosition]) }
|
||||||
holder.layout.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
holder.layout.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
||||||
holder.coverArtId = itemList[listPosition].coverArt
|
holder.coverArtId = currentList[listPosition].coverArt
|
||||||
|
|
||||||
if (Settings.shouldShowArtistPicture) {
|
if (Settings.shouldShowArtistPicture) {
|
||||||
holder.coverArt.visibility = View.VISIBLE
|
holder.coverArt.visibility = View.VISIBLE
|
||||||
val key = FileUtil.getArtistArtKey(itemList[listPosition].name, false)
|
val key = FileUtil.getArtistArtKey(currentList[listPosition].name, false)
|
||||||
imageLoader.loadImage(
|
imageLoader.loadImage(
|
||||||
view = holder.coverArt,
|
view = holder.coverArt,
|
||||||
id = holder.coverArtId,
|
id = holder.coverArtId,
|
||||||
|
@ -81,18 +74,18 @@ class ArtistRowAdapter(
|
||||||
// scrolled up to the "Select Folder" row
|
// scrolled up to the "Select Folder" row
|
||||||
if (listPosition < 0) listPosition = 0
|
if (listPosition < 0) listPosition = 0
|
||||||
|
|
||||||
return getSectionFromName(itemList[listPosition].name ?: " ")
|
return getSectionFromName(currentList[listPosition].name ?: " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSectionForArtist(artistPosition: Int): String {
|
private fun getSectionForArtist(artistPosition: Int): String {
|
||||||
if (artistPosition == 0)
|
if (artistPosition == 0)
|
||||||
return getSectionFromName(itemList[artistPosition].name ?: " ")
|
return getSectionFromName(currentList[artistPosition].name ?: " ")
|
||||||
|
|
||||||
val previousArtistSection = getSectionFromName(
|
val previousArtistSection = getSectionFromName(
|
||||||
itemList[artistPosition - 1].name ?: " "
|
currentList[artistPosition - 1].name ?: " "
|
||||||
)
|
)
|
||||||
val currentArtistSection = getSectionFromName(
|
val currentArtistSection = getSectionFromName(
|
||||||
itemList[artistPosition].name ?: " "
|
currentList[artistPosition].name ?: " "
|
||||||
)
|
)
|
||||||
|
|
||||||
return if (previousArtistSection == currentArtistSection) "" else currentArtistSection
|
return if (previousArtistSection == currentArtistSection) "" else currentArtistSection
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
|
import org.moire.ultrasonic.service.DownloadStatus
|
||||||
|
import org.moire.ultrasonic.service.Downloader
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
|
import org.moire.ultrasonic.view.SongView
|
||||||
|
|
||||||
|
class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ViewModel to use to get the data
|
||||||
|
*/
|
||||||
|
override val listModel: DownloadListModel by viewModels()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the main layout
|
||||||
|
*/
|
||||||
|
override val mainLayout: Int = R.layout.generic_list
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refresh view
|
||||||
|
*/
|
||||||
|
override val refreshListId: Int = R.id.generic_list_refresh
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the RecyclerView
|
||||||
|
*/
|
||||||
|
override val recyclerViewId = R.id.generic_list_recycler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the target in the navigation graph where we should go,
|
||||||
|
* after the user has clicked on an item
|
||||||
|
*/
|
||||||
|
// FIXME
|
||||||
|
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central function to pass a query to the model and return a LiveData object
|
||||||
|
*/
|
||||||
|
override fun getLiveData(args: Bundle?): LiveData<List<DownloadFile>> {
|
||||||
|
return listModel.getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the Adapter for the RecyclerView with a lazy delegate
|
||||||
|
*/
|
||||||
|
override val viewAdapter: DownloadRowAdapter by lazy {
|
||||||
|
DownloadRowAdapter(
|
||||||
|
liveDataItems.value ?: listOf(),
|
||||||
|
{ entry -> onItemClick(entry) },
|
||||||
|
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
||||||
|
onMusicFolderUpdate,
|
||||||
|
requireContext(),
|
||||||
|
viewLifecycleOwner
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContextMenuItemSelected(menuItem: MenuItem, item: DownloadFile): Boolean {
|
||||||
|
// Do nothing
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(item: DownloadFile) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTitle(title: String?) {
|
||||||
|
FragmentTitle.setTitle(this, Util.appContext().getString(R.string.menu_downloads))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadRowAdapter(
|
||||||
|
itemList: List<DownloadFile>,
|
||||||
|
onItemClick: (DownloadFile) -> Unit,
|
||||||
|
onContextMenuClick: (MenuItem, DownloadFile) -> Boolean,
|
||||||
|
onMusicFolderUpdate: (String?) -> Unit,
|
||||||
|
context: Context,
|
||||||
|
val lifecycleOwner: LifecycleOwner
|
||||||
|
) : GenericRowAdapter<DownloadFile>(
|
||||||
|
onItemClick,
|
||||||
|
onContextMenuClick,
|
||||||
|
onMusicFolderUpdate
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
super.submitList(itemList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val starDrawable: Drawable =
|
||||||
|
Util.getDrawableFromAttribute(context, R.attr.star_full)
|
||||||
|
private val starHollowDrawable: Drawable =
|
||||||
|
Util.getDrawableFromAttribute(context, R.attr.star_hollow)
|
||||||
|
|
||||||
|
// Set our layout files
|
||||||
|
override val layout = R.layout.song_list_item
|
||||||
|
override val contextMenuLayout = R.menu.artist_context_menu
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
if (holder is ViewHolder) {
|
||||||
|
val downloadFile = currentList[position]
|
||||||
|
val entry = downloadFile.song
|
||||||
|
holder.title.text = entry.title
|
||||||
|
holder.artist.text = entry.artist
|
||||||
|
holder.star.setImageDrawable(if (entry.starred) starDrawable else starHollowDrawable)
|
||||||
|
|
||||||
|
// Observe download status
|
||||||
|
downloadFile.status.observe(
|
||||||
|
lifecycleOwner,
|
||||||
|
{
|
||||||
|
updateDownloadStatus(downloadFile, holder)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
downloadFile.progress.observe(
|
||||||
|
lifecycleOwner,
|
||||||
|
{
|
||||||
|
updateDownloadStatus(downloadFile, holder)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDownloadStatus(
|
||||||
|
downloadFile: DownloadFile,
|
||||||
|
holder: ViewHolder
|
||||||
|
) {
|
||||||
|
|
||||||
|
var image: Drawable? = null
|
||||||
|
|
||||||
|
when (downloadFile.status.value) {
|
||||||
|
DownloadStatus.DONE -> {
|
||||||
|
image = if (downloadFile.isSaved) SongView.pinImage else SongView.downloadedImage
|
||||||
|
holder.status.text = null
|
||||||
|
}
|
||||||
|
DownloadStatus.DOWNLOADING -> {
|
||||||
|
holder.status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
||||||
|
image = SongView.downloadingImage
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
holder.status.text = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Migrate the image animation stuff from SongView into this class
|
||||||
|
//
|
||||||
|
// if (image != null) {
|
||||||
|
// holder.status.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
// image, null, image, null
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (image === SongView.downloadingImage) {
|
||||||
|
// val frameAnimation = image as AnimationDrawable
|
||||||
|
//
|
||||||
|
// frameAnimation.setVisible(true, true)
|
||||||
|
// frameAnimation.start()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the view properties of an Item row
|
||||||
|
*/
|
||||||
|
class ViewHolder(
|
||||||
|
view: View
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
||||||
|
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
||||||
|
var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
||||||
|
var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
|
||||||
|
var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
|
||||||
|
var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
|
||||||
|
var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
||||||
|
var star: ImageView = view.findViewById(R.id.song_star)
|
||||||
|
var drag: ImageView = view.findViewById(R.id.song_drag)
|
||||||
|
var track: TextView = view.findViewById(R.id.song_track)
|
||||||
|
var title: TextView = view.findViewById(R.id.song_title)
|
||||||
|
var artist: TextView = view.findViewById(R.id.song_artist)
|
||||||
|
var duration: TextView = view.findViewById(R.id.song_duration)
|
||||||
|
var status: TextView = view.findViewById(R.id.song_status)
|
||||||
|
|
||||||
|
init {
|
||||||
|
drag.isVisible = false
|
||||||
|
star.isVisible = false
|
||||||
|
fiveStar1.isVisible = false
|
||||||
|
fiveStar2.isVisible = false
|
||||||
|
fiveStar3.isVisible = false
|
||||||
|
fiveStar4.isVisible = false
|
||||||
|
fiveStar5.isVisible = false
|
||||||
|
check.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of our ViewHolder class
|
||||||
|
*/
|
||||||
|
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadListModel(application: Application) : GenericListModel(application) {
|
||||||
|
private val downloader by inject<Downloader>()
|
||||||
|
|
||||||
|
fun getList(): LiveData<List<DownloadFile>> {
|
||||||
|
return downloader.observableList
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.domain.Artist
|
import org.moire.ultrasonic.domain.Artist
|
||||||
import org.moire.ultrasonic.domain.GenericEntry
|
import org.moire.ultrasonic.domain.GenericEntry
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
import org.moire.ultrasonic.domain.MusicFolder
|
||||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
|
@ -31,7 +32,7 @@ import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||||
* @param T: The type of data which will be used (must extend GenericEntry)
|
* @param T: The type of data which will be used (must extend GenericEntry)
|
||||||
* @param TA: The Adapter to use (must extend GenericRowAdapter)
|
* @param TA: The Adapter to use (must extend GenericRowAdapter)
|
||||||
*/
|
*/
|
||||||
abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> : Fragment() {
|
abstract class GenericListFragment<T : Identifiable, TA : GenericRowAdapter<T>> : Fragment() {
|
||||||
internal val activeServerProvider: ActiveServerProvider by inject()
|
internal val activeServerProvider: ActiveServerProvider by inject()
|
||||||
internal val serverSettingsModel: ServerSettingsModel by viewModel()
|
internal val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||||
internal val imageLoaderProvider: ImageLoaderProvider by inject()
|
internal val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||||
|
@ -90,7 +91,6 @@ abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>>
|
||||||
@Suppress("CommentOverPrivateProperty")
|
@Suppress("CommentOverPrivateProperty")
|
||||||
private val musicFolderObserver = { folders: List<MusicFolder> ->
|
private val musicFolderObserver = { folders: List<MusicFolder> ->
|
||||||
viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
||||||
Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,7 +114,7 @@ abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>>
|
||||||
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTitle(title: String?) {
|
open fun setTitle(title: String?) {
|
||||||
if (title == null) {
|
if (title == null) {
|
||||||
FragmentTitle.setTitle(
|
FragmentTitle.setTitle(
|
||||||
this,
|
this,
|
||||||
|
@ -143,7 +143,7 @@ abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>>
|
||||||
liveDataItems = getLiveData(arguments)
|
liveDataItems = getLiveData(arguments)
|
||||||
|
|
||||||
// Register an observer to update our UI when the data changes
|
// Register an observer to update our UI when the data changes
|
||||||
liveDataItems.observe(viewLifecycleOwner, { newItems -> viewAdapter.setData(newItems) })
|
liveDataItems.observe(viewLifecycleOwner, { newItems -> viewAdapter.submitList(newItems) })
|
||||||
|
|
||||||
// Setup the Music folder handling
|
// Setup the Music folder handling
|
||||||
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
||||||
|
@ -176,8 +176,15 @@ abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>>
|
||||||
return inflater.inflate(mainLayout, container, false)
|
return inflater.inflate(mainLayout, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean
|
||||||
|
|
||||||
|
abstract fun onItemClick(item: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
||||||
|
GenericListFragment<T, TA>() {
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
||||||
val isArtist = (item is Artist)
|
val isArtist = (item is Artist)
|
||||||
|
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
|
@ -263,7 +270,7 @@ abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>>
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onItemClick(item: T) {
|
override fun onItemClick(item: T) {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id)
|
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id)
|
||||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.name)
|
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.name)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -21,20 +22,19 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.domain.GenericEntry
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
import org.moire.ultrasonic.domain.MusicFolder
|
||||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView
|
* An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView
|
||||||
*/
|
*/
|
||||||
abstract class GenericRowAdapter<T : GenericEntry>(
|
abstract class GenericRowAdapter<T : Identifiable>(
|
||||||
val onItemClick: (T) -> Unit,
|
val onItemClick: (T) -> Unit,
|
||||||
val onContextMenuClick: (MenuItem, T) -> Boolean,
|
val onContextMenuClick: (MenuItem, T) -> Boolean,
|
||||||
private val onMusicFolderUpdate: (String?) -> Unit
|
private val onMusicFolderUpdate: (String?) -> Unit
|
||||||
) : ListAdapter<T, RecyclerView.ViewHolder>(GenericDiffCallback()) {
|
) : ListAdapter<T, RecyclerView.ViewHolder>(GenericDiffCallback()) {
|
||||||
|
|
||||||
open var itemList: List<T> = listOf()
|
|
||||||
protected abstract val layout: Int
|
protected abstract val layout: Int
|
||||||
protected abstract val contextMenuLayout: Int
|
protected abstract val contextMenuLayout: Int
|
||||||
|
|
||||||
|
@ -43,15 +43,6 @@ abstract class GenericRowAdapter<T : GenericEntry>(
|
||||||
var musicFolders: List<MusicFolder> = listOf()
|
var musicFolders: List<MusicFolder> = listOf()
|
||||||
var selectedFolder: String? = null
|
var selectedFolder: String? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the data to be displayed in the RecyclerView,
|
|
||||||
* using DiffUtil to efficiently calculate the minimum required changes..
|
|
||||||
*/
|
|
||||||
open fun setData(data: List<T>) {
|
|
||||||
submitList(data)
|
|
||||||
itemList = data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the content and state of the music folder selector row
|
* Sets the content and state of the music folder selector row
|
||||||
*/
|
*/
|
||||||
|
@ -101,9 +92,9 @@ abstract class GenericRowAdapter<T : GenericEntry>(
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
if (selectFolderHeader != null)
|
if (selectFolderHeader != null)
|
||||||
return itemList.size + 1
|
return currentList.size + 1
|
||||||
else
|
else
|
||||||
return itemList.size
|
return currentList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
@ -119,7 +110,7 @@ abstract class GenericRowAdapter<T : GenericEntry>(
|
||||||
downloadMenuItem?.isVisible = !ActiveServerProvider.isOffline()
|
downloadMenuItem?.isVisible = !ActiveServerProvider.isOffline()
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
onContextMenuClick(menuItem, itemList[position])
|
onContextMenuClick(menuItem, currentList[position])
|
||||||
}
|
}
|
||||||
popup.show()
|
popup.show()
|
||||||
return true
|
return true
|
||||||
|
@ -145,7 +136,8 @@ abstract class GenericRowAdapter<T : GenericEntry>(
|
||||||
/**
|
/**
|
||||||
* Calculates the differences between data sets
|
* Calculates the differences between data sets
|
||||||
*/
|
*/
|
||||||
class GenericDiffCallback<T : GenericEntry> : DiffUtil.ItemCallback<T>() {
|
class GenericDiffCallback<T : Identifiable> : DiffUtil.ItemCallback<T>() {
|
||||||
|
@SuppressLint("DiffUtilEquals")
|
||||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,7 +317,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
repeatButton.setOnClickListener {
|
repeatButton.setOnClickListener {
|
||||||
val repeatMode = mediaPlayerController.repeatMode.next()
|
val repeatMode = mediaPlayerController.repeatMode.next()
|
||||||
mediaPlayerController.repeatMode = repeatMode
|
mediaPlayerController.repeatMode = repeatMode
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
when (repeatMode) {
|
when (repeatMode) {
|
||||||
RepeatMode.OFF -> Util.toast(
|
RepeatMode.OFF -> Util.toast(
|
||||||
context, R.string.download_repeat_off
|
context, R.string.download_repeat_off
|
||||||
|
@ -435,7 +435,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
playlistFlipper.displayedChild = 1
|
playlistFlipper.displayedChild = 1
|
||||||
} else {
|
} else {
|
||||||
// Download list and Album art must be updated when Resumed
|
// Download list and Album art must be updated when Resumed
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
onCurrentChanged()
|
onCurrentChanged()
|
||||||
}
|
}
|
||||||
val handler = Handler()
|
val handler = Handler()
|
||||||
|
@ -642,7 +642,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
}
|
}
|
||||||
R.id.menu_remove -> {
|
R.id.menu_remove -> {
|
||||||
mediaPlayerController.removeFromPlaylist(song!!)
|
mediaPlayerController.removeFromPlaylist(song!!)
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.menu_item_screen_on_off -> {
|
R.id.menu_item_screen_on_off -> {
|
||||||
|
@ -697,7 +697,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
R.id.menu_item_clear_playlist -> {
|
R.id.menu_item_clear_playlist -> {
|
||||||
mediaPlayerController.isShufflePlayEnabled = false
|
mediaPlayerController.isShufflePlayEnabled = false
|
||||||
mediaPlayerController.clear()
|
mediaPlayerController.clear()
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.menu_item_save_playlist -> {
|
R.id.menu_item_save_playlist -> {
|
||||||
|
@ -798,7 +798,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
if (cancel!!.isCancellationRequested) return
|
if (cancel!!.isCancellationRequested) return
|
||||||
val mediaPlayerController = mediaPlayerController
|
val mediaPlayerController = mediaPlayerController
|
||||||
if (currentRevision != mediaPlayerController.playListUpdateRevision) {
|
if (currentRevision != mediaPlayerController.playListUpdateRevision) {
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
}
|
}
|
||||||
if (currentPlaying != mediaPlayerController.currentPlaying) {
|
if (currentPlaying != mediaPlayerController.currentPlaying) {
|
||||||
onCurrentChanged()
|
onCurrentChanged()
|
||||||
|
@ -874,7 +874,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDownloadListChanged() {
|
private fun onPlaylistChanged() {
|
||||||
val mediaPlayerController = mediaPlayerController
|
val mediaPlayerController = mediaPlayerController
|
||||||
val list = mediaPlayerController.playList
|
val list = mediaPlayerController.playList
|
||||||
emptyTextView.setText(R.string.download_empty)
|
emptyTextView.setText(R.string.download_empty)
|
||||||
|
@ -907,7 +907,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
|
||||||
item.song.title
|
item.song.title
|
||||||
)
|
)
|
||||||
Util.toast(context, songRemoved)
|
Util.toast(context, songRemoved)
|
||||||
onDownloadListChanged()
|
onPlaylistChanged()
|
||||||
onCurrentChanged()
|
onCurrentChanged()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.domain.Artist
|
import org.moire.ultrasonic.domain.Artist
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
||||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
|
@ -35,7 +36,7 @@ import timber.log.Timber
|
||||||
class DownloadFile(
|
class DownloadFile(
|
||||||
val song: MusicDirectory.Entry,
|
val song: MusicDirectory.Entry,
|
||||||
private val save: Boolean
|
private val save: Boolean
|
||||||
) : KoinComponent, Comparable<DownloadFile> {
|
) : KoinComponent, Identifiable {
|
||||||
val partialFile: File
|
val partialFile: File
|
||||||
val completeFile: File
|
val completeFile: File
|
||||||
private val saveFile: File = FileUtil.getSongFile(song)
|
private val saveFile: File = FileUtil.getSongFile(song)
|
||||||
|
@ -61,6 +62,7 @@ class DownloadFile(
|
||||||
private val activeServerProvider: ActiveServerProvider by inject()
|
private val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
|
||||||
val progress: MutableLiveData<Int> = MutableLiveData(0)
|
val progress: MutableLiveData<Int> = MutableLiveData(0)
|
||||||
|
val status: MutableLiveData<DownloadStatus> = MutableLiveData(DownloadStatus.IDLE)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name))
|
partialFile = File(saveFile.parent, FileUtil.getPartialFile(saveFile.name))
|
||||||
|
@ -204,11 +206,13 @@ class DownloadFile(
|
||||||
val musicService = getMusicService()
|
val musicService = getMusicService()
|
||||||
|
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
|
|
||||||
var inputStream: InputStream? = null
|
var inputStream: InputStream? = null
|
||||||
var outputStream: FileOutputStream? = null
|
var outputStream: FileOutputStream? = null
|
||||||
try {
|
try {
|
||||||
if (saveFile.exists()) {
|
if (saveFile.exists()) {
|
||||||
Timber.i("%s already exists. Skipping.", saveFile)
|
Timber.i("%s already exists. Skipping.", saveFile)
|
||||||
|
status.postValue(DownloadStatus.DONE)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,9 +226,12 @@ class DownloadFile(
|
||||||
} else {
|
} else {
|
||||||
Timber.i("%s already exists. Skipping.", completeFile)
|
Timber.i("%s already exists. Skipping.", completeFile)
|
||||||
}
|
}
|
||||||
|
status.postValue(DownloadStatus.DONE)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status.postValue(DownloadStatus.DOWNLOADING)
|
||||||
|
|
||||||
// Some devices seem to throw error on partial file which doesn't exist
|
// Some devices seem to throw error on partial file which doesn't exist
|
||||||
val needsDownloading: Boolean
|
val needsDownloading: Boolean
|
||||||
val duration = song.duration
|
val duration = song.duration
|
||||||
|
@ -267,6 +274,7 @@ class DownloadFile(
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
|
status.postValue(DownloadStatus.ABORTED)
|
||||||
throw Exception(String.format("Download of '%s' was cancelled", song))
|
throw Exception(String.format("Download of '%s' was cancelled", song))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,6 +283,8 @@ class DownloadFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAndSaveCoverArt()
|
downloadAndSaveCoverArt()
|
||||||
|
|
||||||
|
status.postValue(DownloadStatus.DONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
|
@ -293,7 +303,11 @@ class DownloadFile(
|
||||||
Util.delete(saveFile)
|
Util.delete(saveFile)
|
||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
isFailed = true
|
isFailed = true
|
||||||
if (retryCount > 0) {
|
if (retryCount > 1) {
|
||||||
|
status.postValue(DownloadStatus.RETRYING)
|
||||||
|
--retryCount
|
||||||
|
} else if (retryCount == 1) {
|
||||||
|
status.postValue(DownloadStatus.FAILED)
|
||||||
--retryCount
|
--retryCount
|
||||||
}
|
}
|
||||||
Timber.w(all, "Failed to download '%s'.", song)
|
Timber.w(all, "Failed to download '%s'.", song)
|
||||||
|
@ -389,11 +403,20 @@ class DownloadFile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: DownloadFile): Int {
|
override fun compareTo(other: Identifiable) = compareTo(other as DownloadFile)
|
||||||
|
|
||||||
|
fun compareTo(other: DownloadFile): Int {
|
||||||
return priority.compareTo(other.priority)
|
return priority.compareTo(other.priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val id: String
|
||||||
|
get() = song.id
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_RETRIES = 5
|
const val MAX_RETRIES = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DownloadStatus {
|
||||||
|
IDLE, DOWNLOADING, RETRYING, FAILED, ABORTED, DONE
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package org.moire.ultrasonic.service
|
package org.moire.ultrasonic.service
|
||||||
|
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.PriorityQueue
|
import java.util.PriorityQueue
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.RejectedExecutionException
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
@ -20,7 +22,6 @@ import timber.log.Timber
|
||||||
* This class is responsible for maintaining the playlist and downloading
|
* This class is responsible for maintaining the playlist and downloading
|
||||||
* its items from the network to the filesystem.
|
* its items from the network to the filesystem.
|
||||||
*
|
*
|
||||||
* TODO: Implement LiveData
|
|
||||||
* TODO: Move away from managing the queue with scheduled checks, instead use callbacks when
|
* TODO: Move away from managing the queue with scheduled checks, instead use callbacks when
|
||||||
* Downloads are finished
|
* Downloads are finished
|
||||||
*/
|
*/
|
||||||
|
@ -35,6 +36,8 @@ class Downloader(
|
||||||
private val downloadQueue: PriorityQueue<DownloadFile> = PriorityQueue<DownloadFile>()
|
private val downloadQueue: PriorityQueue<DownloadFile> = PriorityQueue<DownloadFile>()
|
||||||
private val activelyDownloading: MutableList<DownloadFile> = ArrayList()
|
private val activelyDownloading: MutableList<DownloadFile> = ArrayList()
|
||||||
|
|
||||||
|
val observableList: MutableLiveData<List<DownloadFile>> = MutableLiveData<List<DownloadFile>>()
|
||||||
|
|
||||||
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
|
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
|
||||||
|
|
||||||
private val downloadFileCache = LRUCache<MusicDirectory.Entry, DownloadFile>(100)
|
private val downloadFileCache = LRUCache<MusicDirectory.Entry, DownloadFile>(100)
|
||||||
|
@ -58,6 +61,7 @@ class Downloader(
|
||||||
stop()
|
stop()
|
||||||
clearPlaylist()
|
clearPlaylist()
|
||||||
clearBackground()
|
clearBackground()
|
||||||
|
observableList.value = listOf()
|
||||||
Timber.i("Downloader destroyed")
|
Timber.i("Downloader destroyed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,10 +92,21 @@ class Downloader(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkDownloads() {
|
fun checkDownloads() {
|
||||||
if (executorService == null || executorService!!.isTerminated) {
|
if (
|
||||||
|
executorService == null ||
|
||||||
|
executorService!!.isTerminated ||
|
||||||
|
executorService!!.isShutdown
|
||||||
|
) {
|
||||||
start()
|
start()
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
executorService?.execute(downloadChecker)
|
executorService?.execute(downloadChecker)
|
||||||
|
} catch (exception: RejectedExecutionException) {
|
||||||
|
Timber.w(
|
||||||
|
exception,
|
||||||
|
"checkDownloads() can't run, maybe the Downloader is shutting down..."
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +127,8 @@ class Downloader(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the active downloads for failures or completions and remove them
|
// Check the active downloads for failures or completions and remove them
|
||||||
cleanupActiveDownloads()
|
// Store the result in a flag to know if changes have occurred
|
||||||
|
var listChanged = cleanupActiveDownloads()
|
||||||
|
|
||||||
// Check if need to preload more from playlist
|
// Check if need to preload more from playlist
|
||||||
val preloadCount = Settings.preloadCount
|
val preloadCount = Settings.preloadCount
|
||||||
|
@ -134,6 +150,7 @@ class Downloader(
|
||||||
!activelyDownloading.contains(download) &&
|
!activelyDownloading.contains(download) &&
|
||||||
!downloadQueue.contains(download)
|
!downloadQueue.contains(download)
|
||||||
) {
|
) {
|
||||||
|
listChanged = true
|
||||||
downloadQueue.add(download)
|
downloadQueue.add(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,12 +165,21 @@ class Downloader(
|
||||||
if (playlist.indexOf(task) == 1) {
|
if (playlist.indexOf(task) == 1) {
|
||||||
localMediaPlayer.setNextPlayerState(PlayerState.DOWNLOADING)
|
localMediaPlayer.setNextPlayerState(PlayerState.DOWNLOADING)
|
||||||
}
|
}
|
||||||
|
listChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop Executor service when done downloading
|
// Stop Executor service when done downloading
|
||||||
if (activelyDownloading.size == 0) {
|
if (activelyDownloading.size == 0) {
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (listChanged) {
|
||||||
|
updateLiveData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLiveData() {
|
||||||
|
observableList.postValue(downloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startDownloadOnService(task: DownloadFile) {
|
private fun startDownloadOnService(task: DownloadFile) {
|
||||||
|
@ -162,7 +188,12 @@ class Downloader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanupActiveDownloads() {
|
/**
|
||||||
|
* Return true if modifications were made
|
||||||
|
*/
|
||||||
|
private fun cleanupActiveDownloads(): Boolean {
|
||||||
|
val oldSize = activelyDownloading.size
|
||||||
|
|
||||||
activelyDownloading.retainAll {
|
activelyDownloading.retainAll {
|
||||||
when {
|
when {
|
||||||
it.isDownloading -> true
|
it.isDownloading -> true
|
||||||
|
@ -177,6 +208,8 @@ class Downloader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (oldSize != activelyDownloading.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
|
@ -201,13 +234,34 @@ class Downloader(
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Synchronized
|
@get:Synchronized
|
||||||
val downloads: List<DownloadFile?>
|
val all: List<DownloadFile>
|
||||||
get() {
|
get() {
|
||||||
val temp: MutableList<DownloadFile?> = ArrayList()
|
val temp: MutableList<DownloadFile> = ArrayList()
|
||||||
temp.addAll(playlist)
|
|
||||||
temp.addAll(activelyDownloading)
|
temp.addAll(activelyDownloading)
|
||||||
temp.addAll(downloadQueue)
|
temp.addAll(downloadQueue)
|
||||||
return temp.distinct()
|
temp.addAll(playlist)
|
||||||
|
return temp.distinct().sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a list of all DownloadFiles that are currently downloading or waiting for download,
|
||||||
|
* including undownloaded files from the playlist.
|
||||||
|
*/
|
||||||
|
@get:Synchronized
|
||||||
|
val downloads: List<DownloadFile>
|
||||||
|
get() {
|
||||||
|
val temp: MutableList<DownloadFile> = ArrayList()
|
||||||
|
temp.addAll(activelyDownloading)
|
||||||
|
temp.addAll(downloadQueue)
|
||||||
|
temp.addAll(
|
||||||
|
playlist.filter {
|
||||||
|
when (it.status.value) {
|
||||||
|
DownloadStatus.DOWNLOADING -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return temp.distinct().sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -216,11 +270,14 @@ class Downloader(
|
||||||
|
|
||||||
// Cancel all active downloads with a high priority
|
// Cancel all active downloads with a high priority
|
||||||
for (download in activelyDownloading) {
|
for (download in activelyDownloading) {
|
||||||
if (download.priority < 100)
|
if (download.priority < 100) {
|
||||||
download.cancelDownload()
|
download.cancelDownload()
|
||||||
|
activelyDownloading.remove(download)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistUpdateRevision++
|
playlistUpdateRevision++
|
||||||
|
updateLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -230,17 +287,21 @@ class Downloader(
|
||||||
|
|
||||||
// Cancel all active downloads with a low priority
|
// Cancel all active downloads with a low priority
|
||||||
for (download in activelyDownloading) {
|
for (download in activelyDownloading) {
|
||||||
if (download.priority >= 100)
|
if (download.priority >= 100) {
|
||||||
download.cancelDownload()
|
download.cancelDownload()
|
||||||
|
activelyDownloading.remove(download)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun clearActiveDownloads() {
|
fun clearActiveDownloads() {
|
||||||
// Cancel all active downloads with a low priority
|
// Cancel all active downloads
|
||||||
for (download in activelyDownloading) {
|
for (download in activelyDownloading) {
|
||||||
download.cancelDownload()
|
download.cancelDownload()
|
||||||
}
|
}
|
||||||
|
activelyDownloading.clear()
|
||||||
|
updateLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -250,6 +311,7 @@ class Downloader(
|
||||||
}
|
}
|
||||||
playlist.remove(downloadFile)
|
playlist.remove(downloadFile)
|
||||||
playlistUpdateRevision++
|
playlistUpdateRevision++
|
||||||
|
checkDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
|
|
@ -421,14 +421,13 @@ class MediaPlayerController(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions
|
|
||||||
val isJukeboxAvailable: Boolean
|
val isJukeboxAvailable: Boolean
|
||||||
get() {
|
get() {
|
||||||
try {
|
try {
|
||||||
val username = activeServerProvider.getActiveServer().userName
|
val username = activeServerProvider.getActiveServer().userName
|
||||||
return getMusicService().getUser(username).jukeboxRole
|
return getMusicService().getUser(username).jukeboxRole
|
||||||
} catch (e: Exception) {
|
} catch (all: Exception) {
|
||||||
Timber.w(e, "Error getting user information")
|
Timber.w(all, "Error getting user information")
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@ import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.Looper
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
@ -159,7 +161,10 @@ class MediaPlayerService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyDownloaderStopped() {
|
fun notifyDownloaderStopped() {
|
||||||
stopIfIdle()
|
// TODO It would be nice to know if the service really can be stopped instead of just
|
||||||
|
// checking if it is idle once...
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
handler.postDelayed({ stopIfIdle() }, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -356,13 +361,13 @@ class MediaPlayerService : Service() {
|
||||||
Util.broadcastNewTrackInfo(this@MediaPlayerService, currentPlaying.song)
|
Util.broadcastNewTrackInfo(this@MediaPlayerService, currentPlaying.song)
|
||||||
Util.broadcastA2dpMetaDataChange(
|
Util.broadcastA2dpMetaDataChange(
|
||||||
this@MediaPlayerService, playerPosition, currentPlaying,
|
this@MediaPlayerService, playerPosition, currentPlaying,
|
||||||
downloader.downloads.size, downloader.currentPlayingIndex + 1
|
downloader.all.size, downloader.currentPlayingIndex + 1
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Util.broadcastNewTrackInfo(this@MediaPlayerService, null)
|
Util.broadcastNewTrackInfo(this@MediaPlayerService, null)
|
||||||
Util.broadcastA2dpMetaDataChange(
|
Util.broadcastA2dpMetaDataChange(
|
||||||
this@MediaPlayerService, playerPosition, null,
|
this@MediaPlayerService, playerPosition, null,
|
||||||
downloader.downloads.size, downloader.currentPlayingIndex + 1
|
downloader.all.size, downloader.currentPlayingIndex + 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,14 +745,19 @@ class MediaPlayerService : Service() {
|
||||||
private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
|
private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
|
||||||
private const val NOTIFICATION_ID = 3033
|
private const val NOTIFICATION_ID = 3033
|
||||||
|
|
||||||
|
@Volatile
|
||||||
private var instance: MediaPlayerService? = null
|
private var instance: MediaPlayerService? = null
|
||||||
private val instanceLock = Any()
|
private val instanceLock = Any()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getInstance(): MediaPlayerService? {
|
fun getInstance(): MediaPlayerService? {
|
||||||
val context = UApp.applicationContext()
|
val context = UApp.applicationContext()
|
||||||
synchronized(instanceLock) {
|
// Try for twenty times to retrieve a running service,
|
||||||
|
// sleep 100 millis between each try,
|
||||||
|
// and run the block that creates a service only synchronized.
|
||||||
for (i in 0..19) {
|
for (i in 0..19) {
|
||||||
|
if (instance != null) return instance
|
||||||
|
synchronized(instanceLock) {
|
||||||
if (instance != null) return instance
|
if (instance != null) return instance
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(
|
context.startForegroundService(
|
||||||
|
@ -756,11 +766,11 @@ class MediaPlayerService : Service() {
|
||||||
} else {
|
} else {
|
||||||
context.startService(Intent(context, MediaPlayerService::class.java))
|
context.startService(Intent(context, MediaPlayerService::class.java))
|
||||||
}
|
}
|
||||||
Util.sleepQuietly(50L)
|
}
|
||||||
|
Util.sleepQuietly(100L)
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val runningInstance: MediaPlayerService?
|
val runningInstance: MediaPlayerService?
|
||||||
|
|
|
@ -351,9 +351,9 @@ class SongView(context: Context) : UpdateView(context), Checkable, KoinComponent
|
||||||
companion object {
|
companion object {
|
||||||
private var starHollowDrawable: Drawable? = null
|
private var starHollowDrawable: Drawable? = null
|
||||||
private var starDrawable: Drawable? = null
|
private var starDrawable: Drawable? = null
|
||||||
private var pinImage: Drawable? = null
|
var pinImage: Drawable? = null
|
||||||
private var downloadedImage: Drawable? = null
|
var downloadedImage: Drawable? = null
|
||||||
private var downloadingImage: Drawable? = null
|
var downloadingImage: Drawable? = null
|
||||||
private var playingImage: Drawable? = null
|
private var playingImage: Drawable? = null
|
||||||
private var theme: String? = null
|
private var theme: String? = null
|
||||||
private var inflater: LayoutInflater? = null
|
private var inflater: LayoutInflater? = null
|
||||||
|
|
|
@ -25,6 +25,11 @@
|
||||||
a:checkable="true"
|
a:checkable="true"
|
||||||
a:icon="?attr/playlists"
|
a:icon="?attr/playlists"
|
||||||
a:title="@string/button_bar.playlists" />
|
a:title="@string/button_bar.playlists" />
|
||||||
|
<item
|
||||||
|
a:id="@+id/downloadsFragment"
|
||||||
|
a:checkable="true"
|
||||||
|
a:icon="?attr/downloaded"
|
||||||
|
a:title="@string/menu.downloads" />
|
||||||
<item
|
<item
|
||||||
a:id="@+id/sharesFragment"
|
a:id="@+id/sharesFragment"
|
||||||
a:checkable="true"
|
a:checkable="true"
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
android:id="@+id/playlistsToSelectAlbum"
|
android:id="@+id/playlistsToSelectAlbum"
|
||||||
app:destination="@id/trackCollectionFragment" />
|
app:destination="@id/trackCollectionFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/downloadsFragment"
|
||||||
|
android:name="org.moire.ultrasonic.fragment.DownloadsFragment" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/sharesFragment"
|
android:id="@+id/sharesFragment"
|
||||||
android:name="org.moire.ultrasonic.fragment.SharesFragment" >
|
android:name="org.moire.ultrasonic.fragment.SharesFragment" >
|
||||||
|
|
|
@ -121,6 +121,7 @@
|
||||||
<string name="menu.common">Common</string>
|
<string name="menu.common">Common</string>
|
||||||
<string name="menu.deleted_playlist">Deleted playlist %s</string>
|
<string name="menu.deleted_playlist">Deleted playlist %s</string>
|
||||||
<string name="menu.deleted_playlist_error">Failed to delete playlist %s</string>
|
<string name="menu.deleted_playlist_error">Failed to delete playlist %s</string>
|
||||||
|
<string name="menu.downloads">Downloads</string>
|
||||||
<string name="menu.exit">Exit</string>
|
<string name="menu.exit">Exit</string>
|
||||||
<string name="menu.navigation">Navigation</string>
|
<string name="menu.navigation">Navigation</string>
|
||||||
<string name="menu.settings">Settings</string>
|
<string name="menu.settings">Settings</string>
|
||||||
|
@ -213,9 +214,9 @@
|
||||||
<string name="settings.directory_cache_time_60">1 hour</string>
|
<string name="settings.directory_cache_time_60">1 hour</string>
|
||||||
<string name="settings.disc_sort">Sort Songs By Disc</string>
|
<string name="settings.disc_sort">Sort Songs By Disc</string>
|
||||||
<string name="settings.disc_sort_summary">Sort song list by disc number and track number</string>
|
<string name="settings.disc_sort_summary">Sort song list by disc number and track number</string>
|
||||||
<string name="settings.display_bitrate">Display Bitrate And File Suffix</string>
|
<string name="settings.display_bitrate">Display Bitrate and File Suffix</string>
|
||||||
<string name="settings.display_bitrate_summary">Append artist name with bitrate and file suffix</string>
|
<string name="settings.display_bitrate_summary">Append artist name with bitrate and file suffix</string>
|
||||||
<string name="settings.download_transition">Show Downloads On Play</string>
|
<string name="settings.download_transition">Show Downloads on Play</string>
|
||||||
<string name="settings.download_transition_summary">Transition to download activity when starting playback</string>
|
<string name="settings.download_transition_summary">Transition to download activity when starting playback</string>
|
||||||
<string name="settings.gapless_playback">Gapless Playback</string>
|
<string name="settings.gapless_playback">Gapless Playback</string>
|
||||||
<string name="settings.gapless_playback_summary">Enable gapless playback</string>
|
<string name="settings.gapless_playback_summary">Enable gapless playback</string>
|
||||||
|
|
Loading…
Reference in New Issue