Display search results for artists and albums. Only cosmetic for now, there is no action on them.
This commit is contained in:
parent
55ab0ce71c
commit
fa82f13a9c
|
@ -5,19 +5,20 @@ import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.apognu.otter.R
|
import com.github.apognu.otter.R
|
||||||
import com.github.apognu.otter.adapters.TracksAdapter
|
import com.github.apognu.otter.adapters.SearchAdapter
|
||||||
import com.github.apognu.otter.repositories.FavoritesRepository
|
import com.github.apognu.otter.repositories.*
|
||||||
import com.github.apognu.otter.repositories.Repository
|
|
||||||
import com.github.apognu.otter.repositories.SearchRepository
|
|
||||||
import com.github.apognu.otter.utils.untilNetwork
|
import com.github.apognu.otter.utils.untilNetwork
|
||||||
import kotlinx.android.synthetic.main.activity_search.*
|
import kotlinx.android.synthetic.main.activity_search.*
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SearchActivity : AppCompatActivity() {
|
class SearchActivity : AppCompatActivity() {
|
||||||
private lateinit var adapter: TracksAdapter
|
private lateinit var adapter: SearchAdapter
|
||||||
|
|
||||||
|
lateinit var artistsRepository: ArtistsSearchRepository
|
||||||
|
lateinit var albumsRepository: AlbumsSearchRepository
|
||||||
|
lateinit var tracksRepository: TracksSearchRepository
|
||||||
|
|
||||||
lateinit var repository: SearchRepository
|
|
||||||
lateinit var favoritesRepository: FavoritesRepository
|
lateinit var favoritesRepository: FavoritesRepository
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -25,7 +26,7 @@ class SearchActivity : AppCompatActivity() {
|
||||||
|
|
||||||
setContentView(R.layout.activity_search)
|
setContentView(R.layout.activity_search)
|
||||||
|
|
||||||
adapter = TracksAdapter(this, FavoriteListener()).also {
|
adapter = SearchAdapter(this, FavoriteListener()).also {
|
||||||
results.layoutManager = LinearLayoutManager(this)
|
results.layoutManager = LinearLayoutManager(this)
|
||||||
results.adapter = it
|
results.adapter = it
|
||||||
}
|
}
|
||||||
|
@ -43,25 +44,47 @@ class SearchActivity : AppCompatActivity() {
|
||||||
query?.let {
|
query?.let {
|
||||||
val query = URLEncoder.encode(it, "UTF-8")
|
val query = URLEncoder.encode(it, "UTF-8")
|
||||||
|
|
||||||
repository = SearchRepository(this@SearchActivity, query.toLowerCase(Locale.ROOT))
|
tracksRepository = TracksSearchRepository(this@SearchActivity, query.toLowerCase(Locale.ROOT))
|
||||||
|
albumsRepository = AlbumsSearchRepository(this@SearchActivity, query.toLowerCase(Locale.ROOT))
|
||||||
|
artistsRepository = ArtistsSearchRepository(this@SearchActivity, query.toLowerCase(Locale.ROOT))
|
||||||
favoritesRepository = FavoritesRepository(this@SearchActivity)
|
favoritesRepository = FavoritesRepository(this@SearchActivity)
|
||||||
|
|
||||||
search_spinner.visibility = View.VISIBLE
|
search_spinner.visibility = View.VISIBLE
|
||||||
search_no_results.visibility = View.GONE
|
search_no_results.visibility = View.GONE
|
||||||
|
|
||||||
adapter.data.clear()
|
adapter.artists.clear()
|
||||||
|
adapter.albums.clear()
|
||||||
|
adapter.tracks.clear()
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
repository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks, _, _ ->
|
artistsRepository.fetch(Repository.Origin.Network.origin).untilNetwork { artists, _, _ ->
|
||||||
|
when (artists.isEmpty()) {
|
||||||
|
true -> search_no_results.visibility = View.VISIBLE
|
||||||
|
false -> adapter.artists.addAll(artists)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
albumsRepository.fetch(Repository.Origin.Network.origin).untilNetwork { albums, _, _ ->
|
||||||
|
when (albums.isEmpty()) {
|
||||||
|
true -> search_no_results.visibility = View.VISIBLE
|
||||||
|
false -> adapter.albums.addAll(albums)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
tracksRepository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks, _, _ ->
|
||||||
search_spinner.visibility = View.GONE
|
search_spinner.visibility = View.GONE
|
||||||
search_empty.visibility = View.GONE
|
search_empty.visibility = View.GONE
|
||||||
|
|
||||||
when (tracks.isEmpty()) {
|
when (tracks.isEmpty()) {
|
||||||
true -> search_no_results.visibility = View.VISIBLE
|
true -> search_no_results.visibility = View.VISIBLE
|
||||||
false -> adapter.data.addAll(tracks)
|
false -> adapter.tracks.addAll(tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.notifyItemRangeInserted(adapter.data.size, tracks.size)
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +95,7 @@ class SearchActivity : AppCompatActivity() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class FavoriteListener : TracksAdapter.OnFavoriteListener {
|
inner class FavoriteListener : SearchAdapter.OnFavoriteListener {
|
||||||
override fun onToggleFavorite(id: Int, state: Boolean) {
|
override fun onToggleFavorite(id: Int, state: Boolean) {
|
||||||
when (state) {
|
when (state) {
|
||||||
true -> favoritesRepository.addFavorite(id)
|
true -> favoritesRepository.addFavorite(id)
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
package com.github.apognu.otter.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.github.apognu.otter.R
|
||||||
|
import com.github.apognu.otter.utils.*
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
|
import kotlinx.android.synthetic.main.row_track.view.*
|
||||||
|
|
||||||
|
class SearchAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener? = null) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||||
|
interface OnFavoriteListener {
|
||||||
|
fun onToggleFavorite(id: Int, state: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ResultType {
|
||||||
|
Header,
|
||||||
|
Artist,
|
||||||
|
Album,
|
||||||
|
Track
|
||||||
|
}
|
||||||
|
|
||||||
|
val SECTION_COUNT = 3
|
||||||
|
|
||||||
|
var artists: MutableList<Artist> = mutableListOf()
|
||||||
|
var albums: MutableList<Album> = mutableListOf()
|
||||||
|
var tracks: MutableList<Track> = mutableListOf()
|
||||||
|
|
||||||
|
var currentTrack: Track? = null
|
||||||
|
|
||||||
|
override fun getItemCount() = SECTION_COUNT + artists.size + albums.size + tracks.size
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return when (getItemViewType(position)) {
|
||||||
|
ResultType.Header.ordinal -> {
|
||||||
|
if (position == 0) return -1
|
||||||
|
if (position == (artists.size + 1)) return -2
|
||||||
|
return -3
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultType.Artist.ordinal -> artists[position].id.toLong()
|
||||||
|
ResultType.Artist.ordinal -> albums[position - artists.size - 2].id.toLong()
|
||||||
|
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - SECTION_COUNT].id.toLong()
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
if (position == 0) return ResultType.Header.ordinal // Artists header
|
||||||
|
if (position == (artists.size + 1)) return ResultType.Header.ordinal // Albums header
|
||||||
|
if (position == (artists.size + albums.size + 2)) return ResultType.Header.ordinal // Tracks header
|
||||||
|
|
||||||
|
if (position <= artists.size) return ResultType.Artist.ordinal
|
||||||
|
if (position <= artists.size + albums.size + 2) return ResultType.Album.ordinal
|
||||||
|
|
||||||
|
return ResultType.Track.ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = when (viewType) {
|
||||||
|
ResultType.Header.ordinal -> LayoutInflater.from(context).inflate(R.layout.row_search_header, parent, false)
|
||||||
|
else -> LayoutInflater.from(context).inflate(R.layout.row_track, parent, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewHolder(view, context).also {
|
||||||
|
view.setOnClickListener(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val resultType = getItemViewType(position)
|
||||||
|
|
||||||
|
if (resultType == ResultType.Header.ordinal) {
|
||||||
|
context?.let { context ->
|
||||||
|
if (position == 0) holder.title.text = context.getString(R.string.artists)
|
||||||
|
if (position == (artists.size + 1)) holder.title.text = context.getString(R.string.albums)
|
||||||
|
if (position == (artists.size + albums.size + 2)) holder.title.text = context.getString(R.string.tracks)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val item = when (resultType) {
|
||||||
|
ResultType.Artist.ordinal -> {
|
||||||
|
holder.actions.visibility = View.GONE
|
||||||
|
holder.favorite.visibility = View.GONE
|
||||||
|
|
||||||
|
artists[position - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultType.Album.ordinal -> {
|
||||||
|
holder.actions.visibility = View.GONE
|
||||||
|
holder.favorite.visibility = View.GONE
|
||||||
|
|
||||||
|
albums[position - artists.size - 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - SECTION_COUNT]
|
||||||
|
|
||||||
|
else -> tracks[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
Picasso.get()
|
||||||
|
.maybeLoad(maybeNormalizeUrl(item.cover()))
|
||||||
|
.fit()
|
||||||
|
.transform(RoundedCornersTransformation(16, 0))
|
||||||
|
.into(holder.cover)
|
||||||
|
|
||||||
|
holder.title.text = item.title()
|
||||||
|
holder.artist.text = item.subtitle()
|
||||||
|
|
||||||
|
Build.VERSION_CODES.P.onApi(
|
||||||
|
{
|
||||||
|
holder.title.setTypeface(holder.title.typeface, Typeface.DEFAULT.weight)
|
||||||
|
holder.artist.setTypeface(holder.artist.typeface, Typeface.DEFAULT.weight)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
holder.title.typeface = Typeface.create(holder.title.typeface, Typeface.NORMAL)
|
||||||
|
holder.artist.typeface = Typeface.create(holder.artist.typeface, Typeface.NORMAL)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resultType == ResultType.Track.ordinal) {
|
||||||
|
(item as? Track)?.let { track ->
|
||||||
|
context?.let { context ->
|
||||||
|
if (track == currentTrack || track.current) {
|
||||||
|
holder.title.setTypeface(holder.title.typeface, Typeface.BOLD)
|
||||||
|
holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (track.favorite) {
|
||||||
|
true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
|
||||||
|
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.favorite.setOnClickListener {
|
||||||
|
favoriteListener?.let {
|
||||||
|
favoriteListener.onToggleFavorite(track.id, !track.favorite)
|
||||||
|
|
||||||
|
tracks[position - artists.size - albums.size - SECTION_COUNT].favorite = !track.favorite
|
||||||
|
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.actions.setOnClickListener {
|
||||||
|
PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply {
|
||||||
|
inflate(R.menu.row_track)
|
||||||
|
|
||||||
|
setOnMenuItemClickListener {
|
||||||
|
when (it.itemId) {
|
||||||
|
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track)))
|
||||||
|
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track))
|
||||||
|
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track))
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
||||||
|
val handle = view.handle
|
||||||
|
val cover = view.cover
|
||||||
|
val title = view.title
|
||||||
|
val artist = view.artist
|
||||||
|
|
||||||
|
val favorite = view.favorite
|
||||||
|
val actions = view.actions
|
||||||
|
|
||||||
|
override fun onClick(view: View?) {
|
||||||
|
when (getItemViewType(layoutPosition)) {
|
||||||
|
ResultType.Track.ordinal -> {
|
||||||
|
val position = layoutPosition - artists.size - albums.size - SECTION_COUNT
|
||||||
|
|
||||||
|
tracks.subList(position, tracks.size).plus(tracks.subList(0, position)).apply {
|
||||||
|
CommandBus.send(Command.ReplaceQueue(this))
|
||||||
|
|
||||||
|
context.toast("All tracks were added to your queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
package com.github.apognu.otter.repositories
|
package com.github.apognu.otter.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.github.apognu.otter.utils.FunkwhaleResponse
|
import com.github.apognu.otter.utils.*
|
||||||
import com.github.apognu.otter.utils.Track
|
|
||||||
import com.github.apognu.otter.utils.TracksCache
|
|
||||||
import com.github.apognu.otter.utils.TracksResponse
|
|
||||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -12,7 +9,7 @@ import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
|
||||||
class SearchRepository(override val context: Context?, query: String) : Repository<Track, TracksCache>() {
|
class TracksSearchRepository(override val context: Context?, query: String) : Repository<Track, TracksCache>() {
|
||||||
override val cacheId: String? = null
|
override val cacheId: String? = null
|
||||||
override val upstream = HttpUpstream<Track, FunkwhaleResponse<Track>>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&q=$query", object : TypeToken<TracksResponse>() {}.type)
|
override val upstream = HttpUpstream<Track, FunkwhaleResponse<Track>>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&q=$query", object : TypeToken<TracksResponse>() {}.type)
|
||||||
|
|
||||||
|
@ -30,4 +27,20 @@ class SearchRepository(override val context: Context?, query: String) : Reposito
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArtistsSearchRepository(override val context: Context?, query: String) : Repository<Artist, ArtistsCache>() {
|
||||||
|
override val cacheId: String? = null
|
||||||
|
override val upstream = HttpUpstream<Artist, FunkwhaleResponse<Artist>>(HttpUpstream.Behavior.AtOnce, "/api/v1/artists/?playable=true&q=$query", object : TypeToken<ArtistsResponse>() {}.type)
|
||||||
|
|
||||||
|
override fun cache(data: List<Artist>) = ArtistsCache(data)
|
||||||
|
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(ArtistsCache::class.java).deserialize(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumsSearchRepository(override val context: Context?, query: String) : Repository<Album, AlbumsCache>() {
|
||||||
|
override val cacheId: String? = null
|
||||||
|
override val upstream = HttpUpstream<Album, FunkwhaleResponse<Album>>(HttpUpstream.Behavior.AtOnce, "/api/v1/albums/?playable=true&q=$query", object : TypeToken<AlbumsResponse>() {}.type)
|
||||||
|
|
||||||
|
override fun cache(data: List<Album>) = AlbumsCache(data)
|
||||||
|
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(AlbumsCache::class.java).deserialize(reader)
|
||||||
}
|
}
|
|
@ -50,24 +50,38 @@ data class Covers(val original: String)
|
||||||
|
|
||||||
typealias AlbumList = List<Album>
|
typealias AlbumList = List<Album>
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
fun cover(): String?
|
||||||
|
fun title(): String
|
||||||
|
fun subtitle(): String
|
||||||
|
}
|
||||||
|
|
||||||
data class Album(
|
data class Album(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val artist: Artist,
|
val artist: Artist,
|
||||||
val title: String,
|
val title: String,
|
||||||
val cover: Covers
|
val cover: Covers
|
||||||
) {
|
) : SearchResult {
|
||||||
data class Artist(val name: String)
|
data class Artist(val name: String)
|
||||||
|
|
||||||
|
override fun cover() = cover.original
|
||||||
|
override fun title() = title
|
||||||
|
override fun subtitle() = artist.name
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Artist(
|
data class Artist(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
val albums: List<Album>?
|
val albums: List<Album>?
|
||||||
) {
|
) : SearchResult {
|
||||||
data class Album(
|
data class Album(
|
||||||
val title: String,
|
val title: String,
|
||||||
val cover: Covers
|
val cover: Covers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun cover() = albums?.getOrNull(0)?.cover?.original
|
||||||
|
override fun title() = name
|
||||||
|
override fun subtitle() = "Artist"
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Track(
|
data class Track(
|
||||||
|
@ -77,7 +91,7 @@ data class Track(
|
||||||
val album: Album,
|
val album: Album,
|
||||||
val position: Int,
|
val position: Int,
|
||||||
val uploads: List<Upload>
|
val uploads: List<Upload>
|
||||||
) {
|
) : SearchResult {
|
||||||
var current: Boolean = false
|
var current: Boolean = false
|
||||||
var favorite: Boolean = false
|
var favorite: Boolean = false
|
||||||
|
|
||||||
|
@ -103,6 +117,10 @@ data class Track(
|
||||||
else -> uploads.maxBy { it.bitrate } ?: uploads[0]
|
else -> uploads.maxBy { it.bitrate } ?: uploads[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun cover() = album.cover.original
|
||||||
|
override fun title() = title
|
||||||
|
override fun subtitle() = artist.name
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Favorited(val track: Int)
|
data class Favorited(val track: Int)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingVertical="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/AppTheme.Title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
<string name="artists">Artistes</string>
|
<string name="artists">Artistes</string>
|
||||||
<string name="albums">Albums</string>
|
<string name="albums">Albums</string>
|
||||||
|
<string name="tracks">Pistes</string>
|
||||||
<string name="playlists">Playlists</string>
|
<string name="playlists">Playlists</string>
|
||||||
<string name="favorites">Favoris</string>
|
<string name="favorites">Favoris</string>
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
<string name="artists">Artists</string>
|
<string name="artists">Artists</string>
|
||||||
<string name="albums">Albums</string>
|
<string name="albums">Albums</string>
|
||||||
|
<string name="tracks">Tracks</string>
|
||||||
<string name="playlists">Playlists</string>
|
<string name="playlists">Playlists</string>
|
||||||
<string name="favorites">Favorites</string>
|
<string name="favorites">Favorites</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue