Updated rating to be able to use the 5 star and heart rating together

This commit is contained in:
Nite 2025-04-02 19:02:30 +00:00
parent fe5b63ad1f
commit 46e85c27a2
75 changed files with 556 additions and 336 deletions

View File

@ -1,5 +1,5 @@
default: default:
image: registry.gitlab.com/ultrasonic/ci-android:1.1.0 image: registry.gitlab.com/ultrasonic/ci-android:1.2.0
cache: &global_cache cache: &global_cache
key: key:
files: files:

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" /> <bytecodeTargetLevel target="21" />
</component> </component>
</project> </project>

View File

@ -43,7 +43,7 @@ allprojects {
// Set Kotlin JVM target to the same for all subprojects // Set Kotlin JVM target to the same for all subprojects
tasks.withType(KotlinCompile).configureEach { tasks.withType(KotlinCompile).configureEach {
kotlinOptions { kotlinOptions {
jvmTarget = "17" jvmTarget = "21"
} }
} }

View File

@ -13,4 +13,8 @@ dependencies {
android { android {
namespace 'org.moire.ultrasonic.subsonic.domain' namespace 'org.moire.ultrasonic.subsonic.domain'
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
} }

View File

@ -4,6 +4,11 @@ plugins {
apply from: bootstrap.kotlinModule apply from: bootstrap.kotlinModule
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
dependencies { dependencies {
api libs.retrofit api libs.retrofit
api libs.jacksonConverter api libs.jacksonConverter

View File

@ -1,27 +1,27 @@
[versions] [versions]
# You need to run ./gradlew wrapper after updating the version # You need to run ./gradlew wrapper after updating the version
gradle = "8.1.1" gradle = "8.11.1"
navigation = "2.7.5" navigation = "2.8.9"
gradlePlugin = "8.2.0" gradlePlugin = "8.9.1"
androidxcar = "1.2.0" androidxcar = "1.4.0"
androidxcore = "1.12.0" androidxcore = "1.15.0"
ktlint = "1.0.1" ktlint = "1.0.1"
ktlintGradle = "12.0.2" ktlintGradle = "12.0.2"
detekt = "1.23.4" detekt = "1.23.4"
preferences = "1.2.1" preferences = "1.2.1"
media3 = "1.2.0" media3 = "1.6.0"
androidSupport = "1.7.0" androidSupport = "1.9.1"
materialDesign = "1.10.0" materialDesign = "1.12.0"
constraintLayout = "2.1.4" constraintLayout = "2.2.1"
activity = "1.8.1" activity = "1.10.1"
multidex = "2.0.1" multidex = "2.0.1"
room = "2.6.1" room = "2.6.1"
kotlin = "1.9.21" kotlin = "2.1.20"
ksp = "1.9.21-1.0.15" ksp = "2.1.20-1.0.32"
kotlinxCoroutines = "1.7.3" kotlinxCoroutines = "1.7.3"
viewModelKtx = "2.6.2" viewModelKtx = "2.8.7"
swipeRefresh = "1.1.0" swipeRefresh = "1.1.0"
retrofit = "2.9.0" retrofit = "2.9.0"

View File

@ -1,5 +1,5 @@
ext.versions = [ ext.versions = [
minSdk : 21, minSdk : 21,
targetSdk : 33, targetSdk : 33,
compileSdk : 34, compileSdk : 35,
] ]

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -53,7 +53,7 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = "17" jvmTarget = "21"
} }
buildFeatures { buildFeatures {
@ -63,8 +63,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_21
} }
ksp { ksp {

View File

@ -35,8 +35,8 @@ open class AlbumRowDelegate(
open val onContextMenuClick: (MenuItem, Album) -> Boolean open val onContextMenuClick: (MenuItem, Album) -> Boolean
) : ItemViewDelegate<Album, AlbumRowDelegate.ListViewHolder>(), KoinComponent { ) : ItemViewDelegate<Album, AlbumRowDelegate.ListViewHolder>(), KoinComponent {
private val starDrawable: Int = R.drawable.ic_star_full private val starDrawable: Int = R.drawable.rating_star_full
private val starHollowDrawable: Int = R.drawable.ic_star_hollow private val starHollowDrawable: Int = R.drawable.rating_star_hollow
open var layoutType = LayoutType.LIST open var layoutType = LayoutType.LIST

View File

@ -1,13 +1,18 @@
package org.moire.ultrasonic.adapters package org.moire.ultrasonic.adapters
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.view.MenuInflater
import android.view.View import android.view.View
import android.widget.Checkable import android.widget.Checkable
import android.widget.CheckedTextView import android.widget.CheckedTextView
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -31,6 +36,7 @@ import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.themeColor
const val INDICATOR_THICKNESS_INDEFINITE = 5 const val INDICATOR_THICKNESS_INDEFINITE = 5
const val INDICATOR_THICKNESS_DEFINITE = 10 const val INDICATOR_THICKNESS_DEFINITE = 10
@ -50,17 +56,12 @@ class TrackViewHolder(val view: View) :
var entry: Track? = null var entry: Track? = null
private set private set
var songLayout: LinearLayout = view.findViewById(R.id.song_layout) private var songLayout: LinearLayout = view.findViewById(R.id.song_layout)
var check: CheckedTextView = view.findViewById(R.id.song_check) var check: CheckedTextView = view.findViewById(R.id.song_check)
var drag: ImageView = view.findViewById(R.id.song_drag) var drag: ImageView = view.findViewById(R.id.song_drag)
var observableChecked = MutableLiveData(false) var observableChecked = MutableLiveData(false)
private var rating: LinearLayout = view.findViewById(R.id.song_rating)
private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
private var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
private var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
private var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
private var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
private var star: ImageView = view.findViewById(R.id.song_star) private var star: ImageView = view.findViewById(R.id.song_star)
private var track: TextView = view.findViewById(R.id.song_track) private var track: TextView = view.findViewById(R.id.song_track)
private var title: TextView = view.findViewById(R.id.song_title) private var title: TextView = view.findViewById(R.id.song_title)
@ -80,7 +81,6 @@ class TrackViewHolder(val view: View) :
@Suppress("ComplexMethod") @Suppress("ComplexMethod")
fun setSong(song: Track, checkable: Boolean, draggable: Boolean, isSelected: Boolean = false) { fun setSong(song: Track, checkable: Boolean, draggable: Boolean, isSelected: Boolean = false) {
val useFiveStarRating = Settings.useFiveStarRating
entry = song entry = song
val entryDescription = Util.readableEntryDescription(song) val entryDescription = Util.readableEntryDescription(song)
@ -103,7 +103,7 @@ class TrackViewHolder(val view: View) :
if (ActiveServerProvider.isOffline()) { if (ActiveServerProvider.isOffline()) {
star.isGone = true star.isGone = true
} else { } else {
setupStarButtons(song, useFiveStarRating) setupRating(song)
} }
// Instead of blocking the UI thread while looking up the current state, // Instead of blocking the UI thread while looking up the current state,
@ -115,11 +115,7 @@ class TrackViewHolder(val view: View) :
) )
} }
if (useFiveStarRating) { updateRatingDisplay(entry!!.userRating, entry!!.starred)
updateFiveStars(entry?.userRating ?: 0)
} else {
updateSingleStar(entry!!.starred)
}
if (song.isVideo) { if (song.isVideo) {
artist.isGone = true artist.isGone = true
@ -144,9 +140,9 @@ class TrackViewHolder(val view: View) :
if (it.id != song.id) return@launch if (it.id != song.id) return@launch
if (it.rating is HeartRating) { if (it.rating is HeartRating) {
updateSingleStar(it.rating.isHeart) updateRatingDisplay(song.userRating, it.rating.isHeart)
} else if (it.rating is StarRating) { } else if (it.rating is StarRating) {
updateFiveStars(it.rating.starRating.toInt()) updateRatingDisplay(it.rating.starRating.toInt(), song.starred)
} }
} }
} }
@ -187,55 +183,88 @@ class TrackViewHolder(val view: View) :
} }
} }
private fun setupStarButtons(track: Track, useFiveStarRating: Boolean) { private fun setupRating(track: Track) {
if (useFiveStarRating) {
// Hide single star
star.isGone = true
rating.isVisible = true
val rating = if (track.userRating == null) 0 else track.userRating!!
updateFiveStars(rating)
// Five star rating has no click handler because in the
// track view theres not enough space
} else {
star.isVisible = true star.isVisible = true
rating.isGone = true updateRatingDisplay(track.userRating, track.starred)
updateSingleStar(track.starred)
star.setOnClickListener { star.setOnClickListener { toggleHeart(track) }
star.setOnLongClickListener { view -> showRatingPopup(view, track) }
}
private fun toggleHeart(track: Track) {
track.starred = !track.starred track.starred = !track.starred
updateSingleStar(track.starred) updateRatingDisplay(track.userRating, track.starred)
RxBus.ratingSubmitter.onNext( RxBus.ratingSubmitter.onNext(
RatingUpdate(track.id, HeartRating(track.starred)) RatingUpdate(track.id, HeartRating(track.starred))
) )
} }
@Suppress("MagicNumber")
private fun showRatingPopup(view: View, track: Track): Boolean {
val popup = PopupMenu(view.context, view)
val inflater: MenuInflater = popup.menuInflater
inflater.inflate(R.menu.rating, popup.menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) popup.setForceShowIcon(true)
popup.setOnMenuItemClickListener {
val rating = when (it.itemId) {
R.id.popup_rate_1 -> 1
R.id.popup_rate_2 -> 2
R.id.popup_rate_3 -> 3
R.id.popup_rate_4 -> 4
R.id.popup_rate_5 -> 5
else -> 0
} }
track.userRating = rating
updateRatingDisplay(track.userRating, track.starred)
RxBus.ratingSubmitter.onNext(
RatingUpdate(track.id, StarRating(5, rating.toFloat()))
)
true
}
popup.show()
return true
} }
@Suppress("MagicNumber") @Suppress("MagicNumber")
private fun updateFiveStars(rating: Int) { private fun updateRatingDisplay(rating: Int?, starred: Boolean) {
fiveStar1.setImageResource( val ratingDrawable = when (rating) {
if (rating > 0) R.drawable.ic_star_full else R.drawable.ic_star_hollow 1 -> R.drawable.rating_star_1
2 -> R.drawable.rating_star_2
3 -> R.drawable.rating_star_3
4 -> R.drawable.rating_star_4
5 -> R.drawable.rating_star_5
else -> {
R.drawable.rating_star_0
}
}
val layers = if (starred) {
arrayOf(
ResourcesCompat.getDrawable(view.resources, ratingDrawable, null)!!,
ResourcesCompat.getDrawable(
view.resources,
R.drawable.rating_heart_mini_overlay,
null
)!!
) )
fiveStar2.setImageResource( } else {
if (rating > 1) R.drawable.ic_star_full else R.drawable.ic_star_hollow arrayOf(
) ResourcesCompat.getDrawable(view.resources, ratingDrawable, null)!!
fiveStar3.setImageResource(
if (rating > 2) R.drawable.ic_star_full else R.drawable.ic_star_hollow
)
fiveStar4.setImageResource(
if (rating > 3) R.drawable.ic_star_full else R.drawable.ic_star_hollow
)
fiveStar5.setImageResource(
if (rating > 4) R.drawable.ic_star_full else R.drawable.ic_star_hollow
) )
} }
private fun updateSingleStar(starred: Boolean) { val ratingDisplay = LayerDrawable(layers)
ratingDisplay.getDrawable(0).setTint(
view.context.themeColor(com.google.android.material.R.attr.colorOnBackground)
)
if (starred) { if (starred) {
star.setImageResource(R.drawable.ic_star_full) ratingDisplay.getDrawable(1).setTint(
} else { view.context.themeColor(com.google.android.material.R.attr.colorTertiary)
star.setImageResource(R.drawable.ic_star_hollow) )
} }
star.setImageDrawable(ratingDisplay)
} }
private fun updateStatus(status: DownloadState, progress: Int?) { private fun updateStatus(status: DownloadState, progress: Int?) {

View File

@ -8,13 +8,13 @@
package org.moire.ultrasonic.fragment package org.moire.ultrasonic.fragment
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.core.net.toUri
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import java.util.Locale import java.util.Locale
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
@ -60,13 +60,13 @@ class AboutFragment : Fragment() {
webPageButton?.setOnClickListener { webPageButton?.setOnClickListener {
startActivity( startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.about_webpage_url))) Intent(Intent.ACTION_VIEW, getString(R.string.about_webpage_url).toUri())
) )
} }
reportBugButton?.setOnClickListener { reportBugButton?.setOnClickListener {
startActivity( startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.about_report_url))) Intent(Intent.ACTION_VIEW, getString(R.string.about_report_url).toUri())
) )
} }
} }

View File

@ -8,7 +8,6 @@
package org.moire.ultrasonic.fragment package org.moire.ultrasonic.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color.argb import android.graphics.Color.argb
import android.graphics.Point import android.graphics.Point
@ -18,7 +17,6 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.TypedValue
import android.view.GestureDetector import android.view.GestureDetector
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -31,15 +29,12 @@ import android.view.WindowManager
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import android.widget.ViewFlipper import android.widget.ViewFlipper
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.MenuHost import androidx.core.view.MenuHost
@ -105,6 +100,7 @@ import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.ConfirmationDialog import org.moire.ultrasonic.util.ConfirmationDialog
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.themeColor
import org.moire.ultrasonic.util.Util.toast import org.moire.ultrasonic.util.Util.toast
import org.moire.ultrasonic.util.toTrack import org.moire.ultrasonic.util.toTrack
import org.moire.ultrasonic.view.AutoRepeatButton import org.moire.ultrasonic.view.AutoRepeatButton
@ -125,7 +121,6 @@ class PlayerFragment :
private var swipeDistance = 0 private var swipeDistance = 0
private var swipeVelocity = 0 private var swipeVelocity = 0
private var jukeboxAvailable = false private var jukeboxAvailable = false
private var useFiveStarRating = false
private var isEqualizerAvailable = false private var isEqualizerAvailable = false
// Detectors & Callbacks // Detectors & Callbacks
@ -151,6 +146,7 @@ class PlayerFragment :
private lateinit var fiveStar3ImageView: ImageView private lateinit var fiveStar3ImageView: ImageView
private lateinit var fiveStar4ImageView: ImageView private lateinit var fiveStar4ImageView: ImageView
private lateinit var fiveStar5ImageView: ImageView private lateinit var fiveStar5ImageView: ImageView
private lateinit var heartRatingImageView: ImageView
private lateinit var playlistFlipper: ViewFlipper private lateinit var playlistFlipper: ViewFlipper
private lateinit var emptyTextView: TextView private lateinit var emptyTextView: TextView
private lateinit var emptyView: ConstraintLayout private lateinit var emptyView: ConstraintLayout
@ -174,10 +170,15 @@ class PlayerFragment :
private lateinit var repeatButton: MaterialButton private lateinit var repeatButton: MaterialButton
private lateinit var progressBar: SeekBar private lateinit var progressBar: SeekBar
private lateinit var progressIndicator: CircularProgressIndicator private lateinit var progressIndicator: CircularProgressIndicator
private val hollowStar = R.drawable.star_hollow_outline
private val fullStar = R.drawable.star_full_outline private val hollowStar = R.drawable.rating_star_hollow_layered
private val fullStar = R.drawable.rating_star_full_layered
private val hollowHeart = R.drawable.rating_heart_hollow_layered
private val fullHeart = R.drawable.rating_heart_full_layered
private lateinit var hollowStarDrawable: Drawable private lateinit var hollowStarDrawable: Drawable
private lateinit var fullStarDrawable: Drawable private lateinit var fullStarDrawable: Drawable
private lateinit var hollowHeartDrawable: Drawable
private lateinit var fullHeartDrawable: Drawable
private var _binding: CurrentPlayingBinding? = null private var _binding: CurrentPlayingBinding? = null
@ -233,6 +234,7 @@ class PlayerFragment :
fiveStar3ImageView = view.findViewById(R.id.song_five_star_3) fiveStar3ImageView = view.findViewById(R.id.song_five_star_3)
fiveStar4ImageView = view.findViewById(R.id.song_five_star_4) fiveStar4ImageView = view.findViewById(R.id.song_five_star_4)
fiveStar5ImageView = view.findViewById(R.id.song_five_star_5) fiveStar5ImageView = view.findViewById(R.id.song_five_star_5)
heartRatingImageView = view.findViewById(R.id.song_rating_heart)
} }
@Suppress("LongMethod") @Suppress("LongMethod")
@ -265,7 +267,6 @@ class PlayerFragment :
Lifecycle.State.RESUMED Lifecycle.State.RESUMED
) )
useFiveStarRating = Settings.useFiveStarRating
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100 swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
swipeVelocity = swipeDistance swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this) gestureScanner = GestureDetector(context, this)
@ -277,19 +278,26 @@ class PlayerFragment :
updateShuffleButtonState(mediaPlayerManager.isShufflePlayEnabled) updateShuffleButtonState(mediaPlayerManager.isShufflePlayEnabled)
updateRepeatButtonState(mediaPlayerManager.repeatMode) updateRepeatButtonState(mediaPlayerManager.repeatMode)
val ratingLinearLayout = view.findViewById<LinearLayout>(R.id.song_rating)
if (!useFiveStarRating) ratingLinearLayout.isVisible = false
hollowStarDrawable = ResourcesCompat.getDrawable(resources, hollowStar, null)!! hollowStarDrawable = ResourcesCompat.getDrawable(resources, hollowStar, null)!!
fullStarDrawable = ResourcesCompat.getDrawable(resources, fullStar, null)!! fullStarDrawable = ResourcesCompat.getDrawable(resources, fullStar, null)!!
setLayerDrawableColors(hollowStarDrawable as LayerDrawable) setLayerDrawableColors(hollowStarDrawable as LayerDrawable)
setLayerDrawableColors(fullStarDrawable as LayerDrawable) setLayerDrawableColors(fullStarDrawable as LayerDrawable)
hollowHeartDrawable = ResourcesCompat.getDrawable(resources, hollowHeart, null)!!
fullHeartDrawable = ResourcesCompat.getDrawable(resources, fullHeart, null)!!
setLayerDrawableColors(hollowHeartDrawable as LayerDrawable)
setLayerDrawableColors(
fullHeartDrawable as LayerDrawable,
RM.attr.colorAccent,
RM.attr.colorSurface
)
fiveStar1ImageView.setOnClickListener { setSongRating(1) } fiveStar1ImageView.setOnClickListener { setSongRating(1) }
fiveStar2ImageView.setOnClickListener { setSongRating(2) } fiveStar2ImageView.setOnClickListener { setSongRating(2) }
fiveStar3ImageView.setOnClickListener { setSongRating(3) } fiveStar3ImageView.setOnClickListener { setSongRating(3) }
fiveStar4ImageView.setOnClickListener { setSongRating(4) } fiveStar4ImageView.setOnClickListener { setSongRating(4) }
fiveStar5ImageView.setOnClickListener { setSongRating(5) } fiveStar5ImageView.setOnClickListener { setSongRating(5) }
heartRatingImageView.setOnClickListener { setSongHeartRating() }
albumArtImageView.setOnTouchListener { _, me -> albumArtImageView.setOnTouchListener { _, me ->
gestureScanner.onTouchEvent(me) gestureScanner.onTouchEvent(me)
@ -409,6 +417,21 @@ class PlayerFragment :
updateButtonStates(it.state) updateButtonStates(it.state)
} }
rxBusSubscription += RxBus.ratingPublishedObservable.subscribe { update ->
// Ignore updates which are not for the current song
if (update.id != currentSong?.id) return@subscribe
// Ensure UI thread
launch {
if (update.success == false) {
Toast.makeText(context, "Setting rating failed", Toast.LENGTH_SHORT)
.show()
} else {
updateSongRatingDisplay()
}
}
}
// Query the Jukebox state in an IO Context // Query the Jukebox state in an IO Context
ioScope.launch(CommunicationError.getHandler(context)) { ioScope.launch(CommunicationError.getHandler(context)) {
try { try {
@ -537,38 +560,13 @@ class PlayerFragment :
val equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer) val equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer)
val shareMenuItem = menu.findItem(R.id.menu_item_share) val shareMenuItem = menu.findItem(R.id.menu_item_share)
val shareSongMenuItem = menu.findItem(R.id.menu_item_share_song) val shareSongMenuItem = menu.findItem(R.id.menu_item_share_song)
val starMenuItem = menu.findItem(R.id.menu_item_star)
val bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set) val bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set)
val bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete) val bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete)
// Listen to rating changes and update the UI
rxBusSubscription += RxBus.ratingPublishedObservable.subscribe { update ->
// Ignore updates which are not for the current song
if (update.id != currentSong?.id) return@subscribe
// Ensure UI thread
launch {
if (update.success == true && update.rating is HeartRating) {
if (update.rating.isHeart) {
starMenuItem.setIcon(fullStar)
starMenuItem.setTitle(R.string.download_menu_unstar)
} else {
starMenuItem.setIcon(hollowStar)
starMenuItem.setTitle(R.string.download_menu_star)
}
} else if (update.success == false) {
Toast.makeText(context, "Setting rating failed", Toast.LENGTH_SHORT)
.show()
}
}
}
if (isOffline()) { if (isOffline()) {
if (shareMenuItem != null) { if (shareMenuItem != null) {
shareMenuItem.isVisible = false shareMenuItem.isVisible = false
} }
starMenuItem.isVisible = false
if (bookmarkMenuItem != null) { if (bookmarkMenuItem != null) {
bookmarkMenuItem.isVisible = false bookmarkMenuItem.isVisible = false
} }
@ -587,15 +585,11 @@ class PlayerFragment :
currentSong = track currentSong = track
} }
if (useFiveStarRating) starMenuItem.isVisible = false
if (currentSong != null) { if (currentSong != null) {
starMenuItem.setIcon(if (currentSong!!.starred) fullStar else hollowStar)
shareSongMenuItem.isVisible = true shareSongMenuItem.isVisible = true
goToAlbum.isVisible = true goToAlbum.isVisible = true
goToArtist.isVisible = true goToArtist.isVisible = true
} else { } else {
starMenuItem.setIcon(hollowStar)
shareSongMenuItem.isVisible = false shareSongMenuItem.isVisible = false
goToAlbum.isVisible = false goToAlbum.isVisible = false
goToArtist.isVisible = false goToArtist.isVisible = false
@ -731,16 +725,6 @@ class PlayerFragment :
} }
return true return true
} }
R.id.menu_item_star -> {
if (track == null) return true
track.starred = !track.starred
RxBus.ratingSubmitter.onNext(
RatingUpdate(track.id, HeartRating(track.starred))
)
return true
}
R.id.menu_item_bookmark_set -> { R.id.menu_item_bookmark_set -> {
if (track == null) return true if (track == null) return true
@ -1288,30 +1272,36 @@ class PlayerFragment :
private fun updateSongRatingDisplay() { private fun updateSongRatingDisplay() {
val rating = currentSong?.userRating ?: 0 val rating = currentSong?.userRating ?: 0
val isHeartSet = currentSong?.starred ?: false
fiveStar1ImageView.setImageDrawable(getStarForRating(rating, 0)) fiveStar1ImageView.setImageDrawable(getStarForRating(rating, 0))
fiveStar2ImageView.setImageDrawable(getStarForRating(rating, 1)) fiveStar2ImageView.setImageDrawable(getStarForRating(rating, 1))
fiveStar3ImageView.setImageDrawable(getStarForRating(rating, 2)) fiveStar3ImageView.setImageDrawable(getStarForRating(rating, 2))
fiveStar4ImageView.setImageDrawable(getStarForRating(rating, 3)) fiveStar4ImageView.setImageDrawable(getStarForRating(rating, 3))
fiveStar5ImageView.setImageDrawable(getStarForRating(rating, 4)) fiveStar5ImageView.setImageDrawable(getStarForRating(rating, 4))
if (isHeartSet) {
heartRatingImageView.setImageDrawable(fullHeartDrawable)
} else {
heartRatingImageView.setImageDrawable(hollowHeartDrawable)
}
} }
private fun getStarForRating(rating: Int, position: Int): Drawable { private fun getStarForRating(rating: Int, position: Int): Drawable {
return if (rating > position) fullStarDrawable else hollowStarDrawable return if (rating > position) fullStarDrawable else hollowStarDrawable
} }
private fun setLayerDrawableColors(drawable: LayerDrawable) { private fun setLayerDrawableColors(
drawable: LayerDrawable,
innerColor: Int = RM.attr.colorSurface,
borderColor: Int = RM.attr.colorAccent
) {
drawable.apply { drawable.apply {
getDrawable(0).setTint(requireContext().themeColor(RM.attr.colorSurface)) getDrawable(0).setTint(requireContext().themeColor(innerColor))
getDrawable(1).setTint(requireContext().themeColor(RM.attr.colorAccent)) getDrawable(1).setTint(requireContext().themeColor(borderColor))
} }
} }
@ColorInt
fun Context.themeColor(@AttrRes attrRes: Int): Int = TypedValue()
.apply { theme.resolveAttribute(attrRes, this, true) }
.data
private fun setSongRating(rating: Int) { private fun setSongRating(rating: Int) {
if (currentSong == null) return if (currentSong == null) return
currentSong?.userRating = rating currentSong?.userRating = rating
@ -1325,6 +1315,19 @@ class PlayerFragment :
) )
} }
private fun setSongHeartRating() {
if (currentSong == null) return
currentSong?.starred = !(currentSong?.starred ?: true)
updateSongRatingDisplay()
RxBus.ratingSubmitter.onNext(
RatingUpdate(
currentSong!!.id,
HeartRating(currentSong?.starred ?: false)
)
)
}
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private fun showSavePlaylistDialog() { private fun showSavePlaylistDialog() {
val layout = LayoutInflater.from(this.context) val layout = LayoutInflater.from(this.context)

View File

@ -11,11 +11,11 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.SearchRecentSuggestions import android.provider.SearchRecentSuggestions
import android.view.View import android.view.View
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.net.toUri
import androidx.preference.CheckBoxPreference import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
@ -194,7 +194,7 @@ class SettingsFragment :
} }
cacheLocation?.isVisible = true cacheLocation?.isVisible = true
val uri = Uri.parse(Settings.cacheLocationUri) val uri = Settings.cacheLocationUri.toUri()
cacheLocation!!.summary = uri.path cacheLocation!!.summary = uri.path
cacheLocation!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { cacheLocation!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
selectCacheLocation() selectCacheLocation()
@ -342,7 +342,7 @@ class SettingsFragment :
private fun setCacheLocation(path: String) { private fun setCacheLocation(path: String) {
if (path != "") { if (path != "") {
val uri = Uri.parse(path) val uri = path.toUri()
cacheLocation!!.summary = uri.path ?: "" cacheLocation!!.summary = uri.path ?: ""
} }

View File

@ -32,6 +32,11 @@ class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
) )
} }
override fun supportsMimeType(mimeType: String): Boolean {
// TODO: Implement?
return true
}
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> { override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
return executorService.submit<Bitmap> { return executorService.submit<Bitmap> {
decode( decode(
@ -46,10 +51,6 @@ class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
} }
} }
override fun loadBitmap(uri: Uri, options: BitmapFactory.Options?): ListenableFuture<Bitmap> {
return loadBitmap(uri)
}
private fun decode(data: ByteArray): Bitmap { private fun decode(data: ByteArray): Bitmap {
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
return bitmap ?: throw IllegalArgumentException("Could not decode bitmap") return bitmap ?: throw IllegalArgumentException("Could not decode bitmap")

View File

@ -96,25 +96,10 @@ abstract class JukeboxUnimplementedFunctions : Player {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@Deprecated("Deprecated in Java")
override fun hasPrevious(): Boolean {
TODO("Not yet implemented")
}
@Deprecated("Deprecated in Java")
override fun hasPreviousWindow(): Boolean {
TODO("Not yet implemented")
}
override fun hasPreviousMediaItem(): Boolean { override fun hasPreviousMediaItem(): Boolean {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@Deprecated("Deprecated in Java")
override fun previous() {
TODO("Not yet implemented")
}
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun seekToPreviousWindow() { override fun seekToPreviousWindow() {
TODO("Not yet implemented") TODO("Not yet implemented")

View File

@ -9,6 +9,7 @@ package org.moire.ultrasonic.service
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.annotation.OptIn
import androidx.car.app.connection.CarConnection import androidx.car.app.connection.CarConnection
import androidx.media3.common.HeartRating import androidx.media3.common.HeartRating
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -21,11 +22,13 @@ import androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.Rating import androidx.media3.common.Rating
import androidx.media3.common.StarRating import androidx.media3.common.StarRating
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionError
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import androidx.media3.session.SessionResult.RESULT_SUCCESS import androidx.media3.session.SessionResult.RESULT_SUCCESS
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
@ -323,9 +326,9 @@ class MediaLibrarySessionCallback :
) )
.setIconResId( .setIconResId(
if (willHeart) { if (willHeart) {
R.drawable.ic_star_hollow R.drawable.rating_star_hollow
} else { } else {
R.drawable.ic_star_full R.drawable.rating_star_full
} }
) )
.setSessionCommand(sessionCommand) .setSessionCommand(sessionCommand)
@ -371,6 +374,7 @@ class MediaLibrarySessionCallback :
.setEnabled(true) .setEnabled(true)
.build() .build()
@OptIn(UnstableApi::class)
override fun onGetItem( override fun onGetItem(
session: MediaLibraryService.MediaLibrarySession, session: MediaLibraryService.MediaLibrarySession,
browser: MediaSession.ControllerInfo, browser: MediaSession.ControllerInfo,
@ -390,7 +394,7 @@ class MediaLibrarySessionCallback :
) )
} else { } else {
Futures.immediateFuture( Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE) LibraryResult.ofError(SessionError.ERROR_BAD_VALUE)
) )
} }
} }

View File

@ -42,7 +42,10 @@ import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.navigateToCurrent import org.moire.ultrasonic.util.Util.navigateToCurrent
import org.moire.ultrasonic.util.Util.toast import org.moire.ultrasonic.util.Util.toast
import org.moire.ultrasonic.util.getTrackId
import org.moire.ultrasonic.util.launchWithToast import org.moire.ultrasonic.util.launchWithToast
import org.moire.ultrasonic.util.setRating
import org.moire.ultrasonic.util.setStarred
import org.moire.ultrasonic.util.toMediaItem import org.moire.ultrasonic.util.toMediaItem
import org.moire.ultrasonic.util.toTrack import org.moire.ultrasonic.util.toTrack
import timber.log.Timber import timber.log.Timber
@ -235,9 +238,15 @@ class MediaPlayerManager(
rxBusSubscription += RxBus.ratingSubmitterObservable.subscribe { rxBusSubscription += RxBus.ratingSubmitterObservable.subscribe {
// Ensure correct thread // Ensure correct thread
mainScope.launch { mainScope.launch {
// This deals only with the current track! val mediaItem =
if (it.id != currentMediaItem?.toTrack()?.id) return@launch playlist.firstOrNull { item -> item.getTrackId() == it.id } ?: return@launch
setRating(it.rating) if (it.rating is HeartRating) {
mediaItem.setStarred(it.rating.isHeart)
}
if (it.rating is StarRating) {
mediaItem.setRating(it.rating.starRating.toInt())
}
mediaItem.toTrack() // Update item in Converter cache
} }
} }

View File

@ -323,7 +323,11 @@ class PlaybackService :
player.shuffleModeEnabled, player.shuffleModeEnabled,
player.currentMediaItemIndex, player.currentMediaItemIndex,
Settings.preloadCount Settings.preloadCount
).map { it.toTrack() } ).map {
// These items should skip the MediaItemConverter cache.
// The cache contains the controller's items, which may be modified (e.g. their rating)
it.toTrack(false)
}
launch { launch {
DownloadService.download(nextSongs, isHighPriority = true) DownloadService.download(nextSongs, isHighPriority = true)

View File

@ -254,7 +254,7 @@ class ShareHandler {
val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount
Settings.defaultShareExpiration = Settings.defaultShareExpiration =
if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0) { if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0) {
String.format("%d:%s", timeSpanAmount, timeSpanType) String.format(Locale.ROOT, "%d:%s", timeSpanAmount, timeSpanType)
} else { } else {
"" ""
} }

View File

@ -2,7 +2,7 @@ package org.moire.ultrasonic.subsonic
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import androidx.core.net.toUri
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Track import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.service.MusicServiceFactory
@ -27,7 +27,7 @@ class VideoPlayer {
format = "raw" format = "raw"
) )
intent.setDataAndType( intent.setDataAndType(
Uri.parse(url), url?.toUri(),
"video/*" "video/*"
) )
context.startActivity(intent) context.startActivity(intent)

View File

@ -7,11 +7,11 @@
package org.moire.ultrasonic.util package org.moire.ultrasonic.util
import android.annotation.TargetApi
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi
import java.util.Locale import java.util.Locale
/** /**
@ -42,7 +42,7 @@ class LocaleHelper(base: Context?) : ContextWrapper(base) {
config.locale = locale config.locale = locale
} }
@TargetApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
fun setSystemLocale(config: Configuration, locale: Locale?) { fun setSystemLocale(config: Configuration, locale: Locale?) {
config.setLocale(locale) config.setLocale(locale)
} }

View File

@ -139,11 +139,36 @@ fun Track.toMediaItem(mediaId: String = id): MediaItem {
return item return item
} }
/**
* Convenience function to get the Track Id of the MediaItem
*/
fun MediaItem.getTrackId(): String {
return mediaId
}
/**
* Updates the "starred" metadata of the MediaItem
*/
fun MediaItem.setStarred(starred: Boolean) {
mediaMetadata.extras?.putBoolean("starred", starred)
MediaItemConverter.trackCache[mediaId]?.clear()
MediaItemConverter.mediaItemCache[mediaId]?.clear()
}
/**
* Updates the "userRating" metadata of the MediaItem
*/
fun MediaItem.setRating(rating: Int) {
mediaMetadata.extras?.putInt("userRating", rating)
MediaItemConverter.trackCache[mediaId]?.clear()
MediaItemConverter.mediaItemCache[mediaId]?.clear()
}
/** /**
* Extension function to convert a MediaItem to a Track, using the cache if possible * Extension function to convert a MediaItem to a Track, using the cache if possible
*/ */
@Suppress("ComplexMethod") @Suppress("ComplexMethod", "LongMethod")
fun MediaItem.toTrack(): Track { fun MediaItem.toTrack(cacheResult: Boolean = true): Track {
// Check Cache // Check Cache
val cachedTrack = MediaItemConverter.trackCache[mediaId]?.get() val cachedTrack = MediaItemConverter.trackCache[mediaId]?.get()
if (cachedTrack != null) return cachedTrack if (cachedTrack != null) return cachedTrack
@ -202,9 +227,11 @@ fun MediaItem.toTrack(): Track {
track.starred = (mediaMetadata.userRating as HeartRating).isHeart track.starred = (mediaMetadata.userRating as HeartRating).isHeart
} }
if (cacheResult) {
// Add MediaItem and Track to the cache // Add MediaItem and Track to the cache
MediaItemConverter.addToCache(mediaId, track) MediaItemConverter.addToCache(mediaId, track)
MediaItemConverter.addToCache(mediaId, this) MediaItemConverter.addToCache(mediaId, this)
}
return track return track
} }

View File

@ -246,11 +246,6 @@ object Settings {
@JvmStatic @JvmStatic
val overrideLanguage by StringSetting(getKey(R.string.setting_key_override_language), "") val overrideLanguage by StringSetting(getKey(R.string.setting_key_override_language), "")
var useFiveStarRating by BooleanSetting(
getKey(R.string.setting_key_use_five_star_rating),
false
)
var useHwOffload by BooleanSetting(getKey(R.string.setting_key_hardware_offload), false) var useHwOffload by BooleanSetting(getKey(R.string.setting_key_hardware_offload), false)
@JvmStatic @JvmStatic

View File

@ -7,7 +7,7 @@
package org.moire.ultrasonic.util package org.moire.ultrasonic.util
import android.net.Uri import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import java.io.File import java.io.File
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
@ -107,7 +107,7 @@ object Storage {
if (Settings.cacheLocationUri.isBlank()) return Pair(getDefaultRoot(), true) if (Settings.cacheLocationUri.isBlank()) return Pair(getDefaultRoot(), true)
val documentFile = DocumentFile.fromTreeUri( val documentFile = DocumentFile.fromTreeUri(
UApp.applicationContext(), UApp.applicationContext(),
Uri.parse(Settings.cacheLocationUri) Settings.cacheLocationUri.toUri()
) ?: return Pair(getDefaultRoot(), true) ) ?: return Pair(getDefaultRoot(), true)
if (!documentFile.exists()) return Pair(getDefaultRoot(), true) if (!documentFile.exists()) return Pair(getDefaultRoot(), true)
Pair( Pair(

View File

@ -9,6 +9,7 @@ package org.moire.ultrasonic.util
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import androidx.core.content.edit
import androidx.preference.DialogPreference.TargetFragment import androidx.preference.DialogPreference.TargetFragment
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceDialogFragmentCompat import androidx.preference.PreferenceDialogFragmentCompat
@ -49,7 +50,7 @@ class TimeSpanPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat(),
} }
} }
val preference: Preference = preference val preference: Preference = preference
preference.sharedPreferences!!.edit().putString(preference.key, persisted).apply() preference.sharedPreferences!!.edit { putString(preference.key, persisted) }
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -30,14 +30,18 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.text.TextUtils import android.text.TextUtils
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.AnyRes import androidx.annotation.AnyRes
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -614,12 +618,12 @@ object Util {
} }
fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri { fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri {
return Uri.parse( return (
ContentResolver.SCHEME_ANDROID_RESOURCE + ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + context.resources.getResourcePackageName(drawableId) + "://" + context.resources.getResourcePackageName(drawableId) +
'/' + context.resources.getResourceTypeName(drawableId) + '/' + context.resources.getResourceTypeName(drawableId) +
'/' + context.resources.getResourceEntryName(drawableId) '/' + context.resources.getResourceEntryName(drawableId)
) ).toUri()
} }
data class ReadableEntryDescription( data class ReadableEntryDescription(
@ -823,6 +827,11 @@ object Util {
} }
} }
@ColorInt
fun Context.themeColor(@AttrRes attrRes: Int): Int = TypedValue()
.apply { theme.resolveAttribute(attrRes, this, true) }
.data
fun Fragment.navigateToCurrent() { fun Fragment.navigateToCurrent() {
if (Settings.shouldTransitionOnPlayback) { if (Settings.shouldTransitionOnPlayback) {
findNavController().popBackStack(R.id.playerFragment, true) findNavController().popBackStack(R.id.playerFragment, true)

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FFF"
android:pathData="m300,106.02c-62.67,0 -115.03,20.97 -157.03,62.97C100.97,210.98 80,263.35 80,326.02c0,30.67 5.14,60.67 15.47,90 10.33,29.33 28.53,61.48 54.53,96.48 26,35 61,74.52 105,118.52 44,44 99.7,96.29 167.03,156.95L480,840 537.97,787.97C605.3,727.3 661,675.02 705,631.02c44,-44 79,-83.52 105,-118.52 26,-35 44.2,-67.15 54.53,-96.48 10.33,-29.33 15.47,-59.33 15.47,-90 0,-62.67 -20.97,-115.03 -62.97,-157.03 -42,-42 -94.36,-62.97 -157.03,-62.97 -34.67,0 -67.65,7.29 -98.98,21.95C529.68,142.64 502.67,163.33 480,190 457.33,163.33 430.32,142.64 398.98,127.97 367.65,113.3 334.67,106.02 300,106.02Z"
android:strokeWidth="16"
/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/rating_heart_full" />
<item android:drawable="@drawable/rating_heart_full_outline" />
</layer-list>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m300,106.02c-62.67,0 -115.03,20.97 -157.03,62.97C100.97,210.98 80,263.35 80,326.02c0,30.67 5.14,60.67 15.47,90 10.33,29.33 28.53,61.48 54.53,96.48 26,35 61,74.52 105,118.52 44,44 99.7,96.29 167.03,156.95L480,840 537.97,787.97C605.3,727.3 661,675.02 705,631.02c44,-44 79,-83.52 105,-118.52 26,-35 44.2,-67.15 54.53,-96.48 10.33,-29.33 15.47,-59.33 15.47,-90 0,-62.67 -20.97,-115.03 -62.97,-157.03 -42,-42 -94.36,-62.97 -157.03,-62.97 -34.67,0 -67.65,7.29 -98.98,21.95C529.68,142.64 502.67,163.33 480,190 457.33,163.33 430.32,142.64 398.98,127.97 367.65,113.3 334.67,106.02 300,106.02Z"
android:strokeWidth="24"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M300,106.02C237.33,106.02 184.97,126.98 142.97,168.98C100.97,210.98 80,263.35 80,326.02C80,356.68 85.14,386.68 95.47,416.02C105.8,445.35 124,477.5 150,512.5C176,547.5 211,587.02 255,631.02C299,675.02 354.7,727.3 422.03,787.97L480,840L537.97,787.97C605.3,727.3 661,675.02 705,631.02C749,587.02 784,547.5 810,512.5C836,477.5 854.2,445.35 864.53,416.02C874.86,386.68 880,356.68 880,326.02C880,263.35 859.03,210.98 817.03,168.98C775.03,126.98 722.67,106.02 660,106.02C625.33,106.02 592.35,113.3 561.02,127.97C529.68,142.64 502.67,163.33 480,190C457.33,163.33 430.32,142.64 398.98,127.97C367.65,113.3 334.67,106.02 300,106.02zM323.75,214.84C352.57,213.6 376.37,222.66 399.61,238.28C422.84,253.9 438.79,273.8 447.5,297.97L513.75,297.97C522.46,273.8 538.41,253.9 561.64,238.28C584.88,222.66 610.12,214.84 637.42,214.84C672.28,214.84 701.37,226.65 724.61,250.23C747.84,273.82 759.45,303.3 759.45,338.67C759.45,359.31 755.4,380.09 747.27,401.02C739.13,421.94 724.58,445.83 703.67,472.66C682.76,499.48 654.3,531.01 618.28,567.27C582.27,603.52 536.39,647.03 480.63,697.73C424.86,647.03 378.98,603.52 342.97,567.27C306.95,531.01 278.49,499.48 257.58,472.66C236.67,445.83 222.12,421.94 213.98,401.02C205.85,380.09 201.8,359.31 201.8,338.67C201.8,303.3 213.41,273.82 236.64,250.23C259.88,226.65 294.93,216.09 323.75,214.84z"
android:strokeWidth="16"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/rating_heart_hollow" />
<item android:drawable="@drawable/rating_heart_hollow_outline" />
</layer-list>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M300,106.02C237.33,106.02 184.97,126.98 142.97,168.98C100.97,210.98 80,263.35 80,326.02C80,356.68 85.14,386.68 95.47,416.02C105.8,445.35 124,477.5 150,512.5C176,547.5 211,587.02 255,631.02C299,675.02 354.7,727.3 422.03,787.97L480,840L537.97,787.97C605.3,727.3 661,675.02 705,631.02C749,587.02 784,547.5 810,512.5C836,477.5 854.2,445.35 864.53,416.02C874.86,386.68 880,356.68 880,326.02C880,263.35 859.03,210.98 817.03,168.98C775.03,126.98 722.67,106.02 660,106.02C625.33,106.02 592.35,113.3 561.02,127.97C529.68,142.64 502.67,163.33 480,190C457.33,163.33 430.32,142.64 398.98,127.97C367.65,113.3 334.67,106.02 300,106.02zM323.75,214.84C352.57,213.6 376.37,222.66 399.61,238.28C422.84,253.9 438.79,273.8 447.5,297.97L513.75,297.97C522.46,273.8 538.41,253.9 561.64,238.28C584.88,222.66 610.12,214.84 637.42,214.84C672.28,214.84 701.37,226.65 724.61,250.23C747.84,273.82 759.45,303.3 759.45,338.67C759.45,359.31 755.4,380.09 747.27,401.02C739.13,421.94 724.58,445.83 703.67,472.66C682.76,499.48 654.3,531.01 618.28,567.27C582.27,603.52 536.39,647.03 480.63,697.73C424.86,647.03 378.98,603.52 342.97,567.27C306.95,531.01 278.49,499.48 257.58,472.66C236.67,445.83 222.12,421.94 213.98,401.02C205.85,380.09 201.8,359.31 201.8,338.67C201.8,303.3 213.41,273.82 236.64,250.23C259.88,226.65 294.93,216.09 323.75,214.84z"
android:strokeWidth="16"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="m9.882,9.783c-0.696,0 -1.278,0.233 -1.745,0.7C7.671,10.95 7.438,11.532 7.438,12.228c0,0.341 0.057,0.674 0.172,1 0.115,0.326 0.317,0.683 0.606,1.072 0.289,0.389 0.678,0.828 1.167,1.317 0.489,0.489 1.108,1.07 1.856,1.744l0.644,0.578 0.644,-0.578c0.748,-0.674 1.367,-1.255 1.856,-1.744 0.489,-0.489 0.878,-0.928 1.167,-1.317 0.289,-0.389 0.491,-0.746 0.606,-1.072 0.115,-0.326 0.172,-0.659 0.172,-1 0,-0.696 -0.233,-1.278 -0.7,-1.745 -0.467,-0.467 -1.049,-0.7 -1.745,-0.7 -0.385,0 -0.752,0.081 -1.1,0.244 -0.348,0.163 -0.648,0.393 -0.9,0.689 -0.252,-0.296 -0.552,-0.526 -0.9,-0.689C10.634,9.864 10.267,9.783 9.882,9.783Z"
android:strokeWidth="0.177794"
android:fillColor="#f8b1b1"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="m22.911,9.476 l-7.913,-0.682 -3.092,-7.285 -3.092,7.296 -7.913,0.671 6.009,5.205 -1.805,7.737 6.801,-4.105 6.801,4.105 -1.794,-7.737zM11.896,16.927 L6.889,19.99 8.205,14.257 3.765,10.406 9.623,9.898 11.896,4.491l2.287,5.403 5.857,0.508 -4.44,3.851 1.337,5.724z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.906,1.508L8.813,8.805L0.9,9.477L6.91,14.682L5.104,22.418L11.906,18.313L18.707,22.418L16.912,14.682L22.91,9.477L14.998,8.793L11.906,1.508zM11.896,4.49L14.184,9.893L20.039,10.402L15.6,14.252L16.938,19.977L11.896,16.928L11.885,16.93L11.881,16.928L11.875,13.031L8.209,14.271L8.205,14.258L3.766,10.406L9.623,9.898L11.896,4.49z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.906,1.508L8.813,8.805L0.9,9.477L6.91,14.682L5.104,22.418L11.906,18.313L18.707,22.418L16.912,14.682L22.91,9.477L14.998,8.793L11.906,1.508zM11.896,4.49L14.184,9.893L20.039,10.402L15.611,14.242L15.551,14.223L15.545,14.227L11.875,13.041L11.875,13.031L11.859,13.037L11.838,13.029L11.838,13.043L8.209,14.271L8.205,14.258L3.766,10.406L9.623,9.898L11.896,4.49zM11.881,16.721L11.883,16.924L11.883,16.928L11.881,16.928L11.881,16.721z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.906,1.508L8.813,8.805L0.9,9.477L0.971,9.537L6.91,14.682L5.104,22.418L11.906,18.313L18.707,22.418L16.912,14.682L22.91,9.477L14.998,8.793L11.906,1.508zM11.896,4.49L14.184,9.893L20.039,10.402L15.611,14.242L15.551,14.223L15.545,14.227L11.986,13.076L11.988,13.076L9.666,9.959L9.623,9.898L11.896,4.49zM11.881,16.721L11.883,16.924L11.883,16.928L11.881,16.928L11.881,16.721z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.906,1.508L8.813,8.805L0.9,9.477L0.971,9.537L6.91,14.682L5.104,22.418L11.906,18.313L18.707,22.418L16.912,14.682L22.91,9.477L14.998,8.793L11.906,1.508zM11.896,4.49L14.178,9.877L14.135,9.938L14.137,9.943L11.928,12.994L9.666,9.959L9.623,9.898L11.896,4.49zM11.881,16.721L11.883,16.924L11.883,16.928L11.881,16.928L11.881,16.721z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.906,1.508L8.813,8.805L0.9,9.477L0.971,9.537L6.91,14.682L5.104,22.418L11.906,18.313L18.707,22.418L16.912,14.682L22.91,9.477L14.998,8.793L11.906,1.508zM11.881,16.721L11.883,16.924L11.883,16.928L11.881,16.928L11.881,16.721z"
android:strokeWidth="1.10051"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/rating_star_full" />
<item android:drawable="@drawable/rating_star_full_outline" />
</layer-list>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/rating_star_hollow" />
<item android:drawable="@drawable/rating_star_hollow_outline" />
</layer-list>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_star_full" />
<item android:drawable="@drawable/ic_star_full_outline" />
</layer-list>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_star_hollow" />
<item android:drawable="@drawable/ic_star_hollow_outline" />
</layer-list>

View File

@ -42,7 +42,8 @@
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="60dip" a:layout_height="60dip"
a:layout_gravity="center" a:layout_gravity="center"
a:layout_margin="10dip" a:layout_marginHorizontal="60dip"
a:layout_marginVertical="10dip"
a:orientation="horizontal"> a:orientation="horizontal">
<ImageView <ImageView
@ -54,9 +55,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="10dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_2" a:id="@+id/song_five_star_2"
@ -67,9 +67,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="10dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_3" a:id="@+id/song_five_star_3"
@ -80,9 +79,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="10dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_4" a:id="@+id/song_five_star_4"
@ -93,9 +91,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="10dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_5" a:id="@+id/song_five_star_5"
@ -106,9 +103,23 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="10dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView
a:id="@+id/song_rating_heart"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:layout_marginStart="10dip"
a:paddingTop="8dip"
a:paddingBottom="4dip"
a:scaleType="fitCenter"
a:src="@drawable/rating_heart_hollow_layered" />
</LinearLayout> </LinearLayout>

View File

@ -52,9 +52,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="5dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_2" a:id="@+id/song_five_star_2"
@ -65,9 +64,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="5dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_3" a:id="@+id/song_five_star_3"
@ -78,9 +76,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="5dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_4" a:id="@+id/song_five_star_4"
@ -91,9 +88,8 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="5dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView <ImageView
a:id="@+id/song_five_star_5" a:id="@+id/song_five_star_5"
@ -104,9 +100,23 @@
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:importantForAccessibility="no" a:importantForAccessibility="no"
a:padding="5dip"
a:scaleType="fitCenter" a:scaleType="fitCenter"
a:src="@drawable/star_hollow_outline" /> a:src="@drawable/rating_star_hollow_layered" />
<ImageView
a:id="@+id/song_rating_heart"
a:layout_width="0dip"
a:layout_height="fill_parent"
a:layout_weight="1"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:layout_marginStart="10dip"
a:paddingHorizontal="5dip"
a:paddingTop="5dip"
a:scaleType="fitCenter"
a:src="@drawable/rating_heart_hollow_layered" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -67,7 +67,7 @@
a:contentDescription="@string/download.menu_star" a:contentDescription="@string/download.menu_star"
a:gravity="center_horizontal" a:gravity="center_horizontal"
a:padding="4dp" a:padding="4dp"
a:src="@drawable/ic_star_hollow" a:src="@drawable/rating_star_hollow"
app:layout_constraintBottom_toBottomOf="@+id/cover_art" app:layout_constraintBottom_toBottomOf="@+id/cover_art"
app:layout_constraintEnd_toEndOf="@+id/cover_art" /> app:layout_constraintEnd_toEndOf="@+id/cover_art" />

View File

@ -68,7 +68,7 @@
a:layout_marginEnd="20dp" a:layout_marginEnd="20dp"
a:background="@android:color/transparent" a:background="@android:color/transparent"
a:gravity="center_horizontal" a:gravity="center_horizontal"
a:src="@drawable/ic_star_hollow" a:src="@drawable/rating_star_hollow"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toRightOf="@+id/row_album_details" app:layout_constraintLeft_toRightOf="@+id/row_album_details"
app:layout_constraintStart_toEndOf="@+id/row_album_details" app:layout_constraintStart_toEndOf="@+id/row_album_details"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- DON'T CONVERT TO ConstraintLayout! We have been there and it was slower than LinearLayout :) --> <!-- DON'T CONVERT TO ConstraintLayout! We have been there and it was slower than LinearLayout :) -->
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
a:id="@+id/song_layout" a:id="@+id/song_layout"
a:layout_width="fill_parent" a:layout_width="fill_parent"
@ -32,83 +33,16 @@
<include layout="@layout/list_item_track_details" /> <include layout="@layout/list_item_track_details" />
<LinearLayout
a:id="@+id/song_rating"
a:layout_width="wrap_content"
a:layout_height="fill_parent"
a:layout_gravity="center_vertical"
a:orientation="horizontal"
a:visibility="gone"
tools:visibility="visible">
<ImageView
a:id="@+id/song_five_star_1"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:scaleType="centerInside"
a:src="@drawable/ic_star_hollow" />
<ImageView
a:id="@+id/song_five_star_2"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:scaleType="centerInside"
a:src="@drawable/ic_star_hollow" />
<ImageView
a:id="@+id/song_five_star_3"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:scaleType="centerInside"
a:src="@drawable/ic_star_hollow" />
<ImageView
a:id="@+id/song_five_star_4"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:scaleType="centerInside"
a:src="@drawable/ic_star_hollow" />
<ImageView
a:id="@+id/song_five_star_5"
a:layout_width="10dip"
a:layout_height="fill_parent"
a:layout_marginEnd="8dip"
a:background="@android:color/transparent"
a:focusable="false"
a:gravity="center_vertical"
a:importantForAccessibility="no"
a:scaleType="centerInside"
a:src="@drawable/ic_star_hollow" />
</LinearLayout>
<ImageView <ImageView
a:id="@+id/song_star" a:id="@+id/song_star"
a:layout_width="38dp" a:layout_width="40dp"
a:layout_height="fill_parent" a:layout_height="fill_parent"
a:background="@android:color/transparent" a:background="@android:color/transparent"
a:contentDescription="@string/download.menu_star" a:contentDescription="@string/download.menu_star"
a:focusable="false" a:focusable="false"
a:gravity="center_vertical" a:gravity="center_vertical"
a:paddingEnd="8dip" a:paddingEnd="5dip"
a:src="@drawable/ic_star_hollow" a:src="@drawable/rating_star_5" />
a:visibility="gone" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ rating.xml
~ Copyright (C) 2009-2023 Ultrasonic developers
~
~ Distributed under terms of the GNU GPLv3 license.
-->
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/popup_rate_5"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_5"
android:title="@string/menu.rating_5" />
<item android:id="@+id/popup_rate_4"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_4"
android:title="@string/menu.rating_4" />
<item android:id="@+id/popup_rate_3"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_3"
android:title="@string/menu.rating_3" />
<item android:id="@+id/popup_rate_2"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_2"
android:title="@string/menu.rating_2" />
<item android:id="@+id/popup_rate_1"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_1"
android:title="@string/menu.rating_1" />
<item android:id="@+id/popup_rate_0"
android:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_0"
android:title="@string/menu.rating_no_rating" />
</menu>

View File

@ -9,11 +9,6 @@
a:icon="@drawable/media_toggle_list" a:icon="@drawable/media_toggle_list"
app:showAsAction="always" app:showAsAction="always"
a:title="@string/download.toggle_playlist"/> a:title="@string/download.toggle_playlist"/>
<item
a:id="@+id/menu_item_star"
a:icon="@drawable/ic_star_hollow"
app:showAsAction="always"
a:title="@string/download.menu_star"/>
<item <item
a:id="@+id/menu_show_artist" a:id="@+id/menu_show_artist"

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ rating.xml
~ Copyright (C) 2009-2023 Ultrasonic developers
~
~ Distributed under terms of the GNU GPLv3 license.
-->
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/popup_rate_5"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_5"
android:title="@string/menu.rating_5" />
<item android:id="@+id/popup_rate_4"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_4"
android:title="@string/menu.rating_4" />
<item android:id="@+id/popup_rate_3"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_3"
android:title="@string/menu.rating_3" />
<item android:id="@+id/popup_rate_2"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_2"
android:title="@string/menu.rating_2" />
<item android:id="@+id/popup_rate_1"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_1"
android:title="@string/menu.rating_1" />
<item android:id="@+id/popup_rate_0"
app:showAsAction="ifRoom"
android:icon="@drawable/rating_star_0"
android:title="@string/menu.rating_no_rating" />
</menu>

View File

@ -329,5 +329,4 @@
<string name="api.subsonic.upgrade_server">Nekompatibilní verze. Aktualizujte prosím Subsonic server.</string> <string name="api.subsonic.upgrade_server">Nekompatibilní verze. Aktualizujte prosím Subsonic server.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Používat pět hvězdiček pro hodnocení skladeb</string>
</resources> </resources>

View File

@ -415,8 +415,6 @@
<string name="api.subsonic.upgrade_client">Inkompatible Versionen. Bitte die Ultrasonic App aktualisieren.</string> <string name="api.subsonic.upgrade_client">Inkompatible Versionen. Bitte die Ultrasonic App aktualisieren.</string>
<string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den Subsonic Server aktualisieren.</string> <string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den Subsonic Server aktualisieren.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Fünf-Stern Bewertung</string>
<string name="settings.five_star_rating_description">Benutze Bewertungssystem mit fünf Sternen, anstatt Lieder mit nur mit einem Stern zu markieren.</string>
<string name="about.text"><b>Ultrasonic</b> ist ein kostenloser und quelloffener Android-Musikstreaming-Client für Subsonic API (Version 1.7.0 oder höher) kompatible Server. <string name="about.text"><b>Ultrasonic</b> ist ein kostenloser und quelloffener Android-Musikstreaming-Client für Subsonic API (Version 1.7.0 oder höher) kompatible Server.
\n \n
\nMit <b>Ultrasonic</b> können Sie mit Ihrem Subsonic-kompatiblen Medienserver ganz einfach Musik von Ihrem Heimcomputer auf Ihr Android-Telefon streamen oder herunterladen. Die Subsonic-Server-Software erfordert eine zusätzliche, von Ultrasonic unabhängige Konfiguration. \nMit <b>Ultrasonic</b> können Sie mit Ihrem Subsonic-kompatiblen Medienserver ganz einfach Musik von Ihrem Heimcomputer auf Ihr Android-Telefon streamen oder herunterladen. Die Subsonic-Server-Software erfordert eine zusätzliche, von Ultrasonic unabhängige Konfiguration.

View File

@ -429,8 +429,6 @@
<string name="api.subsonic.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android Ultrasonic.</string> <string name="api.subsonic.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android Ultrasonic.</string>
<string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string> <string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Use cinco estrellas para las canciones</string>
<string name="settings.five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones en lugar de simplemente marcar / desmarcar elementos.</string>
<string name="settings.use_hw_offload_title">Utilizar la reproducción por hardware (experimental)</string> <string name="settings.use_hw_offload_title">Utilizar la reproducción por hardware (experimental)</string>
<string name="settings.use_hw_offload_description">Intenta reproducir los medios usando el procesador decodificador de los medios en tu teléfono. Esto puede mejorar el uso de la batería. ¡Algunos usuarios informan de fallos en la reproducción cuando activan esta opción!</string> <string name="settings.use_hw_offload_description">Intenta reproducir los medios usando el procesador decodificador de los medios en tu teléfono. Esto puede mejorar el uso de la batería. ¡Algunos usuarios informan de fallos en la reproducción cuando activan esta opción!</string>
<string name="list_view">Lista</string> <string name="list_view">Lista</string>
@ -446,7 +444,6 @@
<string name="settings.max_bitrate_pinning">Tasa de bits máxima: al fijar una canción de forma permanente</string> <string name="settings.max_bitrate_pinning">Tasa de bits máxima: al fijar una canción de forma permanente</string>
<string name="shortcut_play_random_songs_short">Canciones aleatorias</string> <string name="shortcut_play_random_songs_short">Canciones aleatorias</string>
<string name="shortcut_play_random_songs_long">Reproducir las canciones aleatoriamente</string> <string name="shortcut_play_random_songs_long">Reproducir las canciones aleatoriamente</string>
<string name="download.menu_unstar">No me gusta</string>
<plurals name="n_songs_added_play_now"> <plurals name="n_songs_added_play_now">
<item quantity="one">%d canción añadida a la cola de reproducción</item> <item quantity="one">%d canción añadida a la cola de reproducción</item>
<item quantity="many">%d canciones añadidas a la cola de reproducción</item> <item quantity="many">%d canciones añadidas a la cola de reproducción</item>

View File

@ -381,7 +381,6 @@
<string name="api.subsonic.upgrade_client">Versions incompatibles. Veuillez mette à jour l\'application Android Ultrasonic.</string> <string name="api.subsonic.upgrade_client">Versions incompatibles. Veuillez mette à jour l\'application Android Ultrasonic.</string>
<string name="api.subsonic.upgrade_server">Versions incompatibles. Veuillez mette à jour le serveur Subsonic.</string> <string name="api.subsonic.upgrade_server">Versions incompatibles. Veuillez mette à jour le serveur Subsonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Utiliser les étoiles pour noter les morceaux</string>
<string name="main.albums_by_year">Chronologique</string> <string name="main.albums_by_year">Chronologique</string>
<string name="grid_view">Couverture</string> <string name="grid_view">Couverture</string>
<string name="settings.preload_1000">1000 morceaux</string> <string name="settings.preload_1000">1000 morceaux</string>
@ -408,7 +407,6 @@
<string name="settings.use_id3_offline_summary">Si vous activez ce paramètre, il n\'affichera que la musique que vous avez téléchargée avec Ultrasonic 4.0 ou une version ultérieure. Les téléchargements antérieurs n\'ont pas les métadonnées nécessaires téléchargées. Vous pouvez basculer entre le mode Épingler et le mode Enregistrer pour déclencher le téléchargement des métadonnées manquantes.</string> <string name="settings.use_id3_offline_summary">Si vous activez ce paramètre, il n\'affichera que la musique que vous avez téléchargée avec Ultrasonic 4.0 ou une version ultérieure. Les téléchargements antérieurs n\'ont pas les métadonnées nécessaires téléchargées. Vous pouvez basculer entre le mode Épingler et le mode Enregistrer pour déclencher le téléchargement des métadonnées manquantes.</string>
<string name="settings.show_confirmation_dialog">Afficher le dialogue de confirmation</string> <string name="settings.show_confirmation_dialog">Afficher le dialogue de confirmation</string>
<string name="notification.permission_required">Les notifications sont nécessaires pour la lecture des médias. Vous pouvez accorder cette autorisation à tout moment dans les paramètres Android.</string> <string name="notification.permission_required">Les notifications sont nécessaires pour la lecture des médias. Vous pouvez accorder cette autorisation à tout moment dans les paramètres Android.</string>
<string name="settings.five_star_rating_description">Utiliser un système de notation à cinq étoiles pour les chansons au lieu d\'attribuer des étoiles ou de ne pas en attribuer à certains éléments.</string>
<string name="settings.use_hw_offload_description">Essayez de lire le média à l\'aide de la puce de décodage média de votre téléphone. Ceci peut améliorer l\'utilisation de la batterie.</string> <string name="settings.use_hw_offload_description">Essayez de lire le média à l\'aide de la puce de décodage média de votre téléphone. Ceci peut améliorer l\'utilisation de la batterie.</string>
<string name="supported_server_features">Fonctionnalités prises en charge</string> <string name="supported_server_features">Fonctionnalités prises en charge</string>
<string name="settings.use_hw_offload_title">Utiliser la lecture matérielle (expérimental)</string> <string name="settings.use_hw_offload_title">Utiliser la lecture matérielle (expérimental)</string>

View File

@ -335,5 +335,4 @@
<string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string> <string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Öt csillagos értékelés használata a dalokhoz</string>
</resources> </resources>

View File

@ -325,8 +325,6 @@
<string name="api.subsonic.requested_data_was_not_found">要求されたデータが見つかりませんでした。</string> <string name="api.subsonic.requested_data_was_not_found">要求されたデータが見つかりませんでした。</string>
<string name="api.subsonic.upgrade_client">互換性のないバージョンです。UltrasonicのAndroidアプリをバージョンアップしてください。</string> <string name="api.subsonic.upgrade_client">互換性のないバージョンです。UltrasonicのAndroidアプリをバージョンアップしてください。</string>
<string name="api.subsonic.upgrade_server">互換性のないバージョンです。Subsonicサーバーをバージョンアップしてください。</string> <string name="api.subsonic.upgrade_server">互換性のないバージョンです。Subsonicサーバーをバージョンアップしてください。</string>
<string name="settings.five_star_rating_title">曲に五つ星評価を利用</string>
<string name="settings.five_star_rating_description">楽曲の評価を、スターあり/なし ではなく、5つの星を付ける方式にします。</string>
<string name="list_view">リスト</string> <string name="list_view">リスト</string>
<string name="grid_view">カバー</string> <string name="grid_view">カバー</string>
<string name="supported_server_features">サポートされている機能</string> <string name="supported_server_features">サポートされている機能</string>

View File

@ -430,10 +430,8 @@
<string name="api.subsonic.trial_period_is_over">Prøveperioden er over.</string> <string name="api.subsonic.trial_period_is_over">Prøveperioden er over.</string>
<string name="api.subsonic.upgrade_client">Ukompatible versjoner. Oppgrader Ultrasonic-programmet for Android.</string> <string name="api.subsonic.upgrade_client">Ukompatible versjoner. Oppgrader Ultrasonic-programmet for Android.</string>
<string name="api.subsonic.upgrade_server">Ukompatible versjoner. Oppgrader Subsonic-tjeneren.</string> <string name="api.subsonic.upgrade_server">Ukompatible versjoner. Oppgrader Subsonic-tjeneren.</string>
<string name="settings.five_star_rating_title">Bruk femstjerners vurdering for spor</string>
<string name="list_view">Liste</string> <string name="list_view">Liste</string>
<string name="api.subsonic.not_authorized">Ikke identitetsbekreftet. Sjekk brukertilganger på Subsonic-tjeneren.</string> <string name="api.subsonic.not_authorized">Ikke identitetsbekreftet. Sjekk brukertilganger på Subsonic-tjeneren.</string>
<string name="settings.five_star_rating_description">Bruk femstjerners vurderingssystem for spor istedenfor enkel stjernemerking/opphevelse av stjernemerking av elementer.</string>
<string name="settings.use_hw_offload_title">Bruk maskinvarebasert avspilling (eksperimentelt)</string> <string name="settings.use_hw_offload_title">Bruk maskinvarebasert avspilling (eksperimentelt)</string>
<string name="grid_view">Omslag</string> <string name="grid_view">Omslag</string>
<string name="supported_server_features">Støttede funksjoner</string> <string name="supported_server_features">Støttede funksjoner</string>

View File

@ -4,5 +4,6 @@
<item name="colorPrimaryDark">?attr/colorSurface</item> <item name="colorPrimaryDark">?attr/colorSurface</item>
<item name="colorControlNormal">?attr/colorOnSurface</item> <item name="colorControlNormal">?attr/colorOnSurface</item>
<item name="colorControlHighlight">?attr/colorSecondary</item> <item name="colorControlHighlight">?attr/colorSecondary</item>
<item name="colorTertiary">@color/tertiary</item>
</style> </style>
</resources> </resources>

View File

@ -425,9 +425,6 @@
<string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string> <string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Vijf sterren gebruiken voor nummers</string>
<string name="settings.five_star_rating_description">Toon vijf sterren om nummers te beoordelen in plaats van items toe te voegen aan of te verwijderen uit de favorieten.</string>
<string name="settings.use_hw_offload_title">Hardwarematig afspelen (experimenteel)</string> <string name="settings.use_hw_offload_title">Hardwarematig afspelen (experimenteel)</string>
<string name="settings.use_hw_offload_description">Probeer media af te spelen met behulp van de mediadecoder op je telefoon. Let op: hierdoor kan het accuverbruik toenemen.</string> <string name="settings.use_hw_offload_description">Probeer media af te spelen met behulp van de mediadecoder op je telefoon. Let op: hierdoor kan het accuverbruik toenemen.</string>
<string name="list_view">Lijst</string> <string name="list_view">Lijst</string>

View File

@ -317,7 +317,6 @@
<string name="api.subsonic.upgrade_client">Brak zgodności wersji. Uaktualnij aplikację Ultrasonic na Androida.</string> <string name="api.subsonic.upgrade_client">Brak zgodności wersji. Uaktualnij aplikację Ultrasonic na Androida.</string>
<string name="api.subsonic.upgrade_server">Brak zgodności wersji. Uaktualnij serwer Subsonic.</string> <string name="api.subsonic.upgrade_server">Brak zgodności wersji. Uaktualnij serwer Subsonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">5-gwiazdkowy system ocen utworów</string>
<string name="settings.show_confirmation_dialog_summary">Pokaż okno potwierdzające usunięcie lub odpięcie utworów</string> <string name="settings.show_confirmation_dialog_summary">Pokaż okno potwierdzające usunięcie lub odpięcie utworów</string>
<string name="language.en">Angielski</string> <string name="language.en">Angielski</string>
<string name="settings.scrobble_summary">Pamiętaj o ustawieniu nazwy użytkownika i hasła do usługi Scrobble na serwerze</string> <string name="settings.scrobble_summary">Pamiętaj o ustawieniu nazwy użytkownika i hasła do usługi Scrobble na serwerze</string>
@ -434,7 +433,6 @@
<string name="server_editor.disabled_feature">Jedna lub więcej funkcji zostało wyłączonych ponieważ serwer ich nie obsługiwał. <string name="server_editor.disabled_feature">Jedna lub więcej funkcji zostało wyłączonych ponieważ serwer ich nie obsługiwał.
\nMożesz uruchomić ten test ponownie kiedykolwiek.</string> \nMożesz uruchomić ten test ponownie kiedykolwiek.</string>
<string name="server_menu.demo">Serwer demonstracyjny</string> <string name="server_menu.demo">Serwer demonstracyjny</string>
<string name="settings.five_star_rating_description">Używaj 5-gwiazdkowego systemu ocen utworów zamiast zwykłego oznaczania/odznaczania utworów gwiazdką.</string>
<string name="list_view">Lista</string> <string name="list_view">Lista</string>
<string name="grid_view">Okładka</string> <string name="grid_view">Okładka</string>
<string name="settings.use_hw_offload_description">Spróbuj odtworzyć pliki multimedialne za pomocą układu dekodującego w telefonie. Może to zmniejszyć zużycie baterii!</string> <string name="settings.use_hw_offload_description">Spróbuj odtworzyć pliki multimedialne za pomocą układu dekodującego w telefonie. Może to zmniejszyć zużycie baterii!</string>

View File

@ -425,12 +425,9 @@
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string> <string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string> <string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Avaliar Músicas com Estrelas</string>
<string name="settings.five_star_rating_description">Usar o sistema de classificação de 5 estrelas em vez de estrelar/não estrelar itens.</string>
<string name="settings.use_hw_offload_title">Usar Reprodução por Hardware (experimental)</string> <string name="settings.use_hw_offload_title">Usar Reprodução por Hardware (experimental)</string>
<string name="settings.use_hw_offload_description">Tenta reproduzir a mídia usando o chip decodificador de mídia do telefone. Isso pode melhorar o uso da bateria. Alguns usuários relatam falhas na reprodução ao ativar esta opção!</string> <string name="settings.use_hw_offload_description">Tenta reproduzir a mídia usando o chip decodificador de mídia do telefone. Isso pode melhorar o uso da bateria. Alguns usuários relatam falhas na reprodução ao ativar esta opção!</string>
<string name="jukebox">Jukebox</string> <string name="jukebox">Jukebox</string>
<string name="download.menu_unstar">Desfavoritar</string>
<string name="settings.preload_1000">1000 músicas</string> <string name="settings.preload_1000">1000 músicas</string>
<string name="settings.share_options">Opções</string> <string name="settings.share_options">Opções</string>
<string name="settings.use_id3_offline_summary">Se você ativar esta configuração, ela mostrará apenas as músicas que você baixou com o Ultrasonic 4.0 ou posterior. Os downloads anteriores não têm os metadados necessários baixados. Você pode alternar entre os modos Fixar e Salvar para acionar o download dos metadados ausentes.</string> <string name="settings.use_id3_offline_summary">Se você ativar esta configuração, ela mostrará apenas as músicas que você baixou com o Ultrasonic 4.0 ou posterior. Os downloads anteriores não têm os metadados necessários baixados. Você pode alternar entre os modos Fixar e Salvar para acionar o download dos metadados ausentes.</string>

View File

@ -316,7 +316,6 @@
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string> <string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string> <string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Use classificação de cinco estrelas para músicas</string>
<string name="server_editor.disabled_feature">Um ou mais recursos foram desativados porque o servidor não os suporta. <string name="server_editor.disabled_feature">Um ou mais recursos foram desativados porque o servidor não os suporta.
\nPode executar este teste novamente a qualquer momento.</string> \nPode executar este teste novamente a qualquer momento.</string>
<string name="server_menu.demo">Servidor Demonstração</string> <string name="server_menu.demo">Servidor Demonstração</string>
@ -347,7 +346,6 @@
<string name="buttons.repeat">Repetir</string> <string name="buttons.repeat">Repetir</string>
<string name="buttons.stop">Parar</string> <string name="buttons.stop">Parar</string>
<string name="buttons.next">Próxima</string> <string name="buttons.next">Próxima</string>
<string name="settings.five_star_rating_description">Usar o sistema de classificação de 5 estrelas em vez de estrelar/não estrelar itens.</string>
<string name="grid_view">Cápa</string> <string name="grid_view">Cápa</string>
<string name="list_view">Lista</string> <string name="list_view">Lista</string>
<string name="supported_server_features">Recursos suportados</string> <string name="supported_server_features">Recursos suportados</string>

View File

@ -358,5 +358,4 @@
<string name="api.subsonic.upgrade_server">Несовместимые версии. Пожалуйста, обновите Subsonic сервер.</string> <string name="api.subsonic.upgrade_server">Несовместимые версии. Пожалуйста, обновите Subsonic сервер.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Использовать пятизвездочный рейтинг для песен</string>
</resources> </resources>

View File

@ -393,7 +393,6 @@
<string name="api.subsonic.upgrade_client">版本不兼容,请升级 Ultrasonic 应用。</string> <string name="api.subsonic.upgrade_client">版本不兼容,请升级 Ultrasonic 应用。</string>
<string name="api.subsonic.upgrade_server">版本不兼容,请升级 Subsonic 服务器。</string> <string name="api.subsonic.upgrade_server">版本不兼容,请升级 Subsonic 服务器。</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">为歌曲使用五星评分</string>
<string name="settings.preload_500">500 首歌</string> <string name="settings.preload_500">500 首歌</string>
<string name="settings.use_id3_offline_summary">如果您启用此设置,它将只显示您使用 Ultrasonic 4.0 或更高版本下载的音乐。较早的下载没有下载必要的元数据。您可以在固定和保存模式之间切换,以触发缺失元数据的下载。</string> <string name="settings.use_id3_offline_summary">如果您启用此设置,它将只显示您使用 Ultrasonic 4.0 或更高版本下载的音乐。较早的下载没有下载必要的元数据。您可以在固定和保存模式之间切换,以触发缺失元数据的下载。</string>
<string name="foreground_exception_text">如果媒体通知仍然存在,请按媒体通知中的播放按钮;否则请打开应用程序开始播放,并重新连接会话到控制器</string> <string name="foreground_exception_text">如果媒体通知仍然存在,请按媒体通知中的播放按钮;否则请打开应用程序开始播放,并重新连接会话到控制器</string>
@ -420,7 +419,6 @@
<string name="albumArt">专辑封面</string> <string name="albumArt">专辑封面</string>
<string name="settings.show_confirmation_dialog">展示确认对话框</string> <string name="settings.show_confirmation_dialog">展示确认对话框</string>
<string name="notification.permission_required">需要通知权限才能进行媒体播放。您可以随时在 Android 设置中授予权限。</string> <string name="notification.permission_required">需要通知权限才能进行媒体播放。您可以随时在 Android 设置中授予权限。</string>
<string name="settings.five_star_rating_description">对歌曲使用五星评级系统,而不是简单的星标/取消星标。</string>
<string name="settings.use_hw_offload_title">使用硬件回放(实验性)</string> <string name="settings.use_hw_offload_title">使用硬件回放(实验性)</string>
<string name="settings.use_hw_offload_description">尝试使用手机上的媒体解码器芯片来播放媒体。这可以改善电池使用情况。部分用户报告启用该选项后播放会有问题!</string> <string name="settings.use_hw_offload_description">尝试使用手机上的媒体解码器芯片来播放媒体。这可以改善电池使用情况。部分用户报告启用该选项后播放会有问题!</string>
<string name="list_view">列表</string> <string name="list_view">列表</string>

View File

@ -3,4 +3,5 @@
<color name="background_color_black">#000000</color> <color name="background_color_black">#000000</color>
<color name="selected_menu_dark">#F3F3F3</color> <color name="selected_menu_dark">#F3F3F3</color>
<color name="selected_menu_light">#000000</color> <color name="selected_menu_light">#000000</color>
<color name="tertiary">#ec407a</color>
</resources> </resources>

View File

@ -46,7 +46,6 @@
<string name="setting_key.default_share_greeting" translatable="false">sharingDefaultGreeting</string> <string name="setting_key.default_share_greeting" translatable="false">sharingDefaultGreeting</string>
<string name="setting_key.share_on_server" translatable="false">sharingCreateOnServer</string> <string name="setting_key.share_on_server" translatable="false">sharingCreateOnServer</string>
<string name="setting_key.default_share_expiration" translatable="false">sharingDefaultExpiration</string> <string name="setting_key.default_share_expiration" translatable="false">sharingDefaultExpiration</string>
<string name="setting_key.use_five_star_rating" translatable="false">use_five_star_rating</string>
<string name="setting_key.hardware_offload" translatable="false">use_hw_offload</string> <string name="setting_key.hardware_offload" translatable="false">use_hw_offload</string>
<string name="setting_key.first_run_executed" translatable="false">firstRunExecuted</string> <string name="setting_key.first_run_executed" translatable="false">firstRunExecuted</string>
<string name="setting_key.resume_on_bluetooth_device" translatable="false">resumeOnBluetoothDevice</string> <string name="setting_key.resume_on_bluetooth_device" translatable="false">resumeOnBluetoothDevice</string>

View File

@ -132,6 +132,12 @@
<string name="menu.exit">Exit</string> <string name="menu.exit">Exit</string>
<string name="menu.settings">Settings</string> <string name="menu.settings">Settings</string>
<string name="menu.refresh">Refresh</string> <string name="menu.refresh">Refresh</string>
<string name="menu.rating_no_rating">No Rating</string>
<string name="menu.rating_1">1 Star</string>
<string name="menu.rating_2">2 Stars</string>
<string name="menu.rating_3">3 Stars</string>
<string name="menu.rating_4">4 Stars</string>
<string name="menu.rating_5">5 Stars</string>
<string name="music_library.label">Media Library</string> <string name="music_library.label">Media Library</string>
<string name="music_library.label_offline">Offline Media</string> <string name="music_library.label_offline">Offline Media</string>
<string name="playlist.label">Playlists</string> <string name="playlist.label">Playlists</string>
@ -338,7 +344,6 @@
<string name="download.bookmark_set">Set Bookmark</string> <string name="download.bookmark_set">Set Bookmark</string>
<string name="download.bookmark_delete">Delete Bookmark</string> <string name="download.bookmark_delete">Delete Bookmark</string>
<string name="download.menu_star">Star</string> <string name="download.menu_star">Star</string>
<string name="download.menu_unstar">Unstar</string>
<string name="download.menu_clear_playlist">Clear Playlist</string> <string name="download.menu_clear_playlist">Clear Playlist</string>
<string name="button_bar.shares">Shares</string> <string name="button_bar.shares">Shares</string>
<string name="select_share.empty">No shares available on server</string> <string name="select_share.empty">No shares available on server</string>
@ -441,8 +446,6 @@
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string> <string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.five_star_rating_title">Use five star rating for songs</string>
<string name="settings.five_star_rating_description">Use five star rating system for songs instead of simply starring/unstarring items.</string>
<string name="settings.use_hw_offload_title">Use hardware playback (experimental)</string> <string name="settings.use_hw_offload_title">Use hardware playback (experimental)</string>
<string name="settings.use_hw_offload_description">Try to play the media using the media decoder chip on your phone. This can improve battery usage. Some users report glitches in playback when they activate this option!</string> <string name="settings.use_hw_offload_description">Try to play the media using the media decoder chip on your phone. This can improve battery usage. Some users report glitches in playback when they activate this option!</string>
<string name="list_view">List</string> <string name="list_view">List</string>

View File

@ -19,23 +19,27 @@
<style name="UltrasonicTheme.DayNight" parent="Theme.Material3.DynamicColors.DayNight"> <style name="UltrasonicTheme.DayNight" parent="Theme.Material3.DynamicColors.DayNight">
<item name="colorControlNormal">?attr/colorOnSurface</item> <item name="colorControlNormal">?attr/colorOnSurface</item>
<item name="colorControlHighlight">?attr/colorSecondary</item> <item name="colorControlHighlight">?attr/colorSecondary</item>
<item name="colorTertiary">@color/tertiary</item>
</style> </style>
<style name="UltrasonicTheme.Black" parent="Theme.Material3.Dark"> <style name="UltrasonicTheme.Black" parent="Theme.Material3.Dark">
<item name="colorPrimaryDark">?attr/backgroundColor</item> <item name="colorPrimaryDark">?attr/backgroundColor</item>
<item name="android:colorBackground">@color/background_color_black</item> <item name="android:colorBackground">@color/background_color_black</item>
<item name="colorControlNormal">?attr/colorOnSurface</item> <item name="colorControlNormal">?attr/colorOnSurface</item>
<item name="colorTertiary">@color/tertiary</item>
</style> </style>
<style name="UltrasonicTheme.Dark" parent="Theme.Material3.DynamicColors.Dark"> <style name="UltrasonicTheme.Dark" parent="Theme.Material3.DynamicColors.Dark">
<item name="colorPrimaryDark">?attr/colorSurface</item> <item name="colorPrimaryDark">?attr/colorSurface</item>
<item name="colorControlNormal">?attr/colorOnSurface</item> <item name="colorControlNormal">?attr/colorOnSurface</item>
<item name="colorControlHighlight">?attr/colorSecondary</item> <item name="colorControlHighlight">?attr/colorSecondary</item>
<item name="colorTertiary">@color/tertiary</item>
</style> </style>
<style name="UltrasonicTheme.Light" parent="Theme.Material3.DynamicColors.Light"> <style name="UltrasonicTheme.Light" parent="Theme.Material3.DynamicColors.Light">
<item name="colorControlNormal">?attr/colorOnSurface</item> <item name="colorControlNormal">?attr/colorOnSurface</item>
<item name="colorControlHighlight">?attr/colorSecondary</item> <item name="colorControlHighlight">?attr/colorSecondary</item>
<item name="colorTertiary">@color/tertiary</item>
</style> </style>
</resources> </resources>

View File

@ -105,12 +105,6 @@
a:key="@string/setting_key.pause_on_bluetooth_device" a:key="@string/setting_key.pause_on_bluetooth_device"
a:title="@string/settings.playback.pause_on_bluetooth_device" a:title="@string/settings.playback.pause_on_bluetooth_device"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<CheckBoxPreference
a:defaultValue="false"
a:key="@string/setting_key.use_five_star_rating"
a:summary="@string/settings.five_star_rating_description"
a:title="@string/settings.five_star_rating_title"
app:iconSpaceReserved="false" />
<CheckBoxPreference <CheckBoxPreference
a:defaultValue="false" a:defaultValue="false"
a:key="@string/setting_key.hardware_offload" a:key="@string/setting_key.hardware_offload"