mirror of
https://github.com/apognu/otter
synced 2025-02-17 03:10:35 +01:00
Added experimental radios support. Fixed linter and fastlane metadata.
This commit is contained in:
parent
3fb0bb55a4
commit
fd1741ca53
2
.editorconfig
Normal file
2
.editorconfig
Normal file
@ -0,0 +1,2 @@
|
||||
[*.{kt,kts}]
|
||||
indent_size=2
|
@ -54,7 +54,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
throw Exception(getString(R.string.login_error_hostname_https))
|
||||
}
|
||||
|
||||
if (scheme == null) hostname = "https://${hostname}"
|
||||
if (scheme == null) hostname = "https://$hostname"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val message =
|
||||
|
@ -15,7 +15,7 @@ import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||
import kotlinx.android.synthetic.main.row_album.view.*
|
||||
import kotlinx.android.synthetic.main.row_artist.view.art
|
||||
|
||||
class AlbumsAdapter(val context: Context?, val listener: OnAlbumClickListener) : FunkwhaleAdapter<Album, AlbumsAdapter.ViewHolder>() {
|
||||
class AlbumsAdapter(val context: Context?, private val listener: OnAlbumClickListener) : FunkwhaleAdapter<Album, AlbumsAdapter.ViewHolder>() {
|
||||
interface OnAlbumClickListener {
|
||||
fun onClick(view: View?, album: Album)
|
||||
}
|
||||
|
@ -4,16 +4,14 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.AlbumsGridFragment
|
||||
import com.github.apognu.otter.fragments.ArtistsFragment
|
||||
import com.github.apognu.otter.fragments.FavoritesFragment
|
||||
import com.github.apognu.otter.fragments.PlaylistsFragment
|
||||
import com.github.apognu.otter.fragments.*
|
||||
import com.github.apognu.otter.utils.Settings
|
||||
|
||||
class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
var tabs = mutableListOf<Fragment>()
|
||||
|
||||
override fun getCount(): Int {
|
||||
return 4
|
||||
return if (Settings.areExperimentsEnabled()) 5 else 4
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
@ -21,13 +19,25 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm
|
||||
return it
|
||||
}
|
||||
|
||||
val fragment = when (position) {
|
||||
0 -> ArtistsFragment()
|
||||
1 -> AlbumsGridFragment()
|
||||
2 -> PlaylistsFragment()
|
||||
3 -> FavoritesFragment()
|
||||
else -> ArtistsFragment()
|
||||
}
|
||||
val fragment =
|
||||
if (Settings.areExperimentsEnabled()) {
|
||||
when (position) {
|
||||
0 -> ArtistsFragment()
|
||||
1 -> AlbumsGridFragment()
|
||||
2 -> PlaylistsFragment()
|
||||
3 -> RadiosFragment()
|
||||
4 -> FavoritesFragment()
|
||||
else -> ArtistsFragment()
|
||||
}
|
||||
} else {
|
||||
when (position) {
|
||||
0 -> ArtistsFragment()
|
||||
1 -> AlbumsGridFragment()
|
||||
2 -> PlaylistsFragment()
|
||||
3 -> FavoritesFragment()
|
||||
else -> ArtistsFragment()
|
||||
}
|
||||
}
|
||||
|
||||
tabs.add(position, fragment)
|
||||
|
||||
@ -35,12 +45,23 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): String {
|
||||
return when (position) {
|
||||
0 -> context.getString(R.string.artists)
|
||||
1 -> context.getString(R.string.albums)
|
||||
2 -> context.getString(R.string.playlists)
|
||||
3 -> context.getString(R.string.favorites)
|
||||
else -> ""
|
||||
return if (Settings.areExperimentsEnabled()) {
|
||||
when (position) {
|
||||
0 -> context.getString(R.string.artists)
|
||||
1 -> context.getString(R.string.albums)
|
||||
2 -> context.getString(R.string.playlists)
|
||||
3 -> context.getString(R.string.radios)
|
||||
4 -> context.getString(R.string.favorites)
|
||||
else -> ""
|
||||
}
|
||||
} else {
|
||||
when (position) {
|
||||
0 -> context.getString(R.string.artists)
|
||||
1 -> context.getString(R.string.albums)
|
||||
2 -> context.getString(R.string.playlists)
|
||||
3 -> context.getString(R.string.favorites)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -75,7 +75,6 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
holder.artist.setTypeface(holder.artist.typeface, Typeface.NORMAL)
|
||||
})
|
||||
|
||||
|
||||
if (track.track == currentTrack) {
|
||||
holder.title.setTypeface(holder.title.typeface, Typeface.BOLD)
|
||||
holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD)
|
||||
@ -145,7 +144,7 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||
CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition))
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
||||
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
|
||||
|
@ -0,0 +1,45 @@
|
||||
package com.github.apognu.otter.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.FunkwhaleAdapter
|
||||
import com.github.apognu.otter.utils.Radio
|
||||
import kotlinx.android.synthetic.main.row_radio.view.*
|
||||
|
||||
class RadiosAdapter(val context: Context?, private val listener: OnRadioClickListener) : FunkwhaleAdapter<Radio, RadiosAdapter.ViewHolder>() {
|
||||
interface OnRadioClickListener {
|
||||
fun onClick(holder: View?, radio: Radio)
|
||||
}
|
||||
|
||||
override fun getItemCount() = data.size
|
||||
|
||||
override fun getItemId(position: Int) = data[position].id.toLong()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RadiosAdapter.ViewHolder {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.row_radio, parent, false)
|
||||
|
||||
return ViewHolder(view, listener).also {
|
||||
view.setOnClickListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RadiosAdapter.ViewHolder, position: Int) {
|
||||
val radio = data[position]
|
||||
|
||||
holder.name.text = radio.name
|
||||
holder.description.text = radio.description
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View, val listener: OnRadioClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
||||
val name = view.name
|
||||
val description = view.description
|
||||
|
||||
override fun onClick(view: View?) {
|
||||
listener.onClick(view, data[layoutPosition])
|
||||
}
|
||||
}
|
||||
}
|
@ -108,7 +108,7 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||
}
|
||||
}
|
||||
|
||||
if (position == (artists.size + albums.size + 2)){
|
||||
if (position == (artists.size + albums.size + 2)) {
|
||||
holder.title.text = context.getString(R.string.tracks)
|
||||
holder.itemView.visibility = View.VISIBLE
|
||||
|
||||
|
@ -74,7 +74,6 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||
holder.artist.typeface = Typeface.create(holder.artist.typeface, Typeface.NORMAL)
|
||||
})
|
||||
|
||||
|
||||
if (track == currentTrack || track.current) {
|
||||
holder.title.setTypeface(holder.title.typeface, Typeface.BOLD)
|
||||
holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD)
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.github.apognu.otter.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.adapters.RadiosAdapter
|
||||
import com.github.apognu.otter.repositories.RadiosRepository
|
||||
import com.github.apognu.otter.utils.Command
|
||||
import com.github.apognu.otter.utils.CommandBus
|
||||
import com.github.apognu.otter.utils.Radio
|
||||
import kotlinx.android.synthetic.main.fragment_radios.*
|
||||
|
||||
class RadiosFragment : FunkwhaleFragment<Radio, RadiosAdapter>() {
|
||||
override val viewRes = R.layout.fragment_radios
|
||||
override val recycler: RecyclerView get() = radios
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = RadiosAdapter(context, RadioClickListener())
|
||||
repository = RadiosRepository(context)
|
||||
}
|
||||
|
||||
inner class RadioClickListener : RadiosAdapter.OnRadioClickListener {
|
||||
override fun onClick(holder: View?, radio: Radio) {
|
||||
CommandBus.send(Command.PlayRadio(radio))
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
@ -43,6 +44,8 @@ class PlayerService : Service() {
|
||||
|
||||
private var progressCache = Triple(0, 0, 0)
|
||||
|
||||
private lateinit var radioPlayer: RadioPlayer
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
watchEventBus()
|
||||
|
||||
@ -53,6 +56,7 @@ class PlayerService : Service() {
|
||||
super.onCreate()
|
||||
|
||||
queue = QueueManager(this)
|
||||
radioPlayer = RadioPlayer(this)
|
||||
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
@ -142,6 +146,8 @@ class PlayerService : Service() {
|
||||
}
|
||||
|
||||
is Command.ReplaceQueue -> {
|
||||
if (!message.fromRadio) radioPlayer.stop()
|
||||
|
||||
queue.replace(message.queue)
|
||||
player.prepare(queue.datasources, true, true)
|
||||
|
||||
@ -188,6 +194,11 @@ class PlayerService : Service() {
|
||||
|
||||
is Command.ClearQueue -> queue.clear()
|
||||
|
||||
is Command.PlayRadio -> {
|
||||
queue.clear()
|
||||
radioPlayer.play(message.radio)
|
||||
}
|
||||
|
||||
is Command.SetRepeatMode -> player.repeatMode = message.mode
|
||||
}
|
||||
|
||||
@ -415,6 +426,15 @@ class PlayerService : Service() {
|
||||
queue.current = player.currentWindowIndex
|
||||
mediaControlsManager.updateNotification(queue.current(), player.playWhenReady)
|
||||
|
||||
if (queue.get().isNotEmpty() && queue.current() == queue.get().last() && radioPlayer.isActive()) {
|
||||
GlobalScope.launch(IO) {
|
||||
if (radioPlayer.lock.tryAcquire()) {
|
||||
radioPlayer.prepareNextTrack()
|
||||
radioPlayer.lock.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cache.set(
|
||||
this@PlayerService,
|
||||
"current",
|
||||
|
@ -0,0 +1,92 @@
|
||||
package com.github.apognu.otter.playback
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResult
|
||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class RadioSessionBody(val radio_type: String, val custom_radio: Int)
|
||||
data class RadioSession(val id: Int)
|
||||
data class RadioTrackBody(val session: Int)
|
||||
data class RadioTrack(val position: Int, val track: RadioTrackID)
|
||||
data class RadioTrackID(val id: Int)
|
||||
|
||||
class RadioPlayer(val context: Context) {
|
||||
val lock = Semaphore(1)
|
||||
|
||||
private var currentRadio: Radio? = null
|
||||
private var session: Int? = null
|
||||
|
||||
fun play(radio: Radio) {
|
||||
currentRadio = radio
|
||||
session = null
|
||||
|
||||
GlobalScope.launch(IO) {
|
||||
createSession()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
currentRadio = null
|
||||
session = null
|
||||
}
|
||||
|
||||
fun isActive() = currentRadio != null && session != null
|
||||
|
||||
private suspend fun createSession() {
|
||||
currentRadio?.let { radio ->
|
||||
try {
|
||||
val body = Gson().toJson(RadioSessionBody("custom", radio.id))
|
||||
val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/sessions/"))
|
||||
.authorize()
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.awaitObjectResult(gsonDeserializerOf(RadioSession::class.java))
|
||||
|
||||
session = result.get().id
|
||||
|
||||
prepareNextTrack(true)
|
||||
} catch (e: Exception) {
|
||||
withContext(Main) {
|
||||
context.toast(context.getString(R.string.radio_playback_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun prepareNextTrack(first: Boolean = false) {
|
||||
session?.let { session ->
|
||||
try {
|
||||
val body = Gson().toJson(RadioTrackBody(session))
|
||||
val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/tracks/"))
|
||||
.authorize()
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body)
|
||||
.awaitObjectResult(gsonDeserializerOf(RadioTrack::class.java))
|
||||
|
||||
val track = Fuel.get(mustNormalizeUrl("/api/v1/tracks/${result.get().track.id}/"))
|
||||
.authorize()
|
||||
.awaitObjectResult(gsonDeserializerOf(Track::class.java))
|
||||
|
||||
if (first) {
|
||||
CommandBus.send(Command.ReplaceQueue(listOf(track.get()), true))
|
||||
} else {
|
||||
CommandBus.send(Command.AddToQueue(listOf(track.get())))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Main) {
|
||||
context.toast(context.getString(R.string.radio_playback_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.utils.FunkwhaleResponse
|
||||
import com.github.apognu.otter.utils.Radio
|
||||
import com.github.apognu.otter.utils.RadiosCache
|
||||
import com.github.apognu.otter.utils.RadiosResponse
|
||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.io.BufferedReader
|
||||
|
||||
class RadiosRepository(override val context: Context?) : Repository<Radio, RadiosCache>() {
|
||||
override val cacheId = "radios"
|
||||
override val upstream = HttpUpstream<Radio, FunkwhaleResponse<Radio>>(HttpUpstream.Behavior.Progressive, "/api/v1/radios/radios/", object : TypeToken<RadiosResponse>() {}.type)
|
||||
|
||||
override fun cache(data: List<Radio>) = RadiosCache(data)
|
||||
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(RadiosCache::class.java).deserialize(reader)
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package com.github.apognu.otter.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.github.apognu.otter.activities.FwCredentials
|
||||
import com.github.kittinunf.fuel.Fuel
|
||||
import com.github.kittinunf.fuel.core.FuelError
|
||||
|
@ -20,10 +20,11 @@ sealed class Command {
|
||||
|
||||
class AddToQueue(val tracks: List<Track>) : Command()
|
||||
class PlayNext(val track: Track) : Command()
|
||||
class ReplaceQueue(val queue: List<Track>) : Command()
|
||||
class ReplaceQueue(val queue: List<Track>, val fromRadio: Boolean = false) : Command()
|
||||
class RemoveFromQueue(val track: Track) : Command()
|
||||
class MoveFromQueue(val oldPosition: Int, val newPosition: Int) : Command()
|
||||
object ClearQueue : Command()
|
||||
class PlayRadio(val radio: Radio) : Command()
|
||||
|
||||
class SetRepeatMode(val mode: Int) : Command()
|
||||
|
||||
|
@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.BrowseFragment
|
||||
import com.github.apognu.otter.repositories.Repository
|
||||
import com.github.kittinunf.fuel.core.Request
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.RequestCreator
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
@ -69,4 +70,12 @@ fun <T> T.applyOnApi(api: Int, block: T.() -> T): T {
|
||||
fun Picasso.maybeLoad(url: String?): RequestCreator {
|
||||
if (url == null) return load(R.drawable.cover)
|
||||
else return load(url)
|
||||
}
|
||||
}
|
||||
|
||||
fun Request.authorize(): Request {
|
||||
return this.apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ class AlbumsCache(data: List<Album>) : CacheItem<Album>(data)
|
||||
class TracksCache(data: List<Track>) : CacheItem<Track>(data)
|
||||
class PlaylistsCache(data: List<Playlist>) : CacheItem<Playlist>(data)
|
||||
class PlaylistTracksCache(data: List<PlaylistTrack>) : CacheItem<PlaylistTrack>(data)
|
||||
class RadiosCache(data: List<Radio>) : CacheItem<Radio>(data)
|
||||
class FavoritedCache(data: List<Int>) : CacheItem<Int>(data)
|
||||
class QueueCache(data: List<Track>) : CacheItem<Track>(data)
|
||||
|
||||
@ -46,6 +47,10 @@ data class PlaylistTracksResponse(override val count: Int, override val next: St
|
||||
override fun getData() = results
|
||||
}
|
||||
|
||||
data class RadiosResponse(override val count: Int, override val next: String?, val results: List<Radio>) : FunkwhaleResponse<Radio>() {
|
||||
override fun getData() = results
|
||||
}
|
||||
|
||||
data class Covers(val original: String)
|
||||
|
||||
typealias AlbumList = List<Album>
|
||||
@ -133,4 +138,10 @@ data class Playlist(
|
||||
val duration: Int
|
||||
)
|
||||
|
||||
data class PlaylistTrack(val track: Track)
|
||||
data class PlaylistTrack(val track: Track)
|
||||
|
||||
data class Radio(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val description: String
|
||||
)
|
@ -16,6 +16,10 @@ fun Any.log(message: String) {
|
||||
Log.d("FUNKWHALE", "${this.javaClass.simpleName}: $message")
|
||||
}
|
||||
|
||||
fun Any.log() {
|
||||
Log.d("FUNKWHALE", this.toString())
|
||||
}
|
||||
|
||||
fun maybeNormalizeUrl(url: String?): String? {
|
||||
if (url == null || url.isEmpty()) return null
|
||||
|
||||
@ -56,4 +60,5 @@ object Settings {
|
||||
fun hasAccessToken() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).contains("access_token")
|
||||
fun getAccessToken(): String = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token", "")
|
||||
fun isAnonymous() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getBoolean("anonymous", false)
|
||||
}
|
||||
fun areExperimentsEnabled() = PowerPreference.getDefaultFile().getBoolean("experiments", false)
|
||||
}
|
||||
|
62
app/src/main/res/layout/fragment_radios.xml
Normal file
62
app/src/main/res/layout/fragment_radios.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swiper"
|
||||
style="@style/AppTheme.Fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/radios"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
tools:itemCount="10"
|
||||
tools:listitem="@layout/row_playlist" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
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"
|
||||
android:text="@string/radios" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,8 +1,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@layout/fragment_queue">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/queue"
|
||||
@ -15,7 +14,7 @@
|
||||
android:id="@+id/placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginBottom="64dp"
|
||||
android:drawableTop="@drawable/ottericon"
|
||||
|
50
app/src/main/res/layout/row_radio.xml
Normal file
50
app/src/main/res/layout/row_radio.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:transitionGroup="true"
|
||||
tools:showIn="@layout/fragment_radios">
|
||||
|
||||
<com.github.apognu.otter.views.SquareImageView
|
||||
android:id="@+id/art"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/cover"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
style="@style/AppTheme.ItemTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
tools:text="Hard Rock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
tools:text="Lorem ipsum dolor sit amet" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -55,6 +55,7 @@
|
||||
<string name="albums">Albums</string>
|
||||
<string name="tracks">Pistes</string>
|
||||
<string name="playlists">Playlists</string>
|
||||
<string name="radios">Radios</string>
|
||||
<string name="favorites">Favoris</string>
|
||||
|
||||
<string name="playback_media_controls">Contrôle de lecture</string>
|
||||
@ -99,6 +100,8 @@
|
||||
<string name="track_info_details_track_bitrate">Bitrate</string>
|
||||
<string name="track_info_details_track_instance">Instance Funkwhale</string>
|
||||
|
||||
<string name="radio_playback_error">Une erreur s\'est produite lors de la lecture de cette radio</string>
|
||||
|
||||
<string name="logout_title">Déconnexion</string>
|
||||
<string name="logout_content">Etes-vous certains de vouloir vous déconnecter de votre instance Funkwhale ?</string>
|
||||
|
||||
|
@ -55,6 +55,7 @@
|
||||
<string name="albums">Albums</string>
|
||||
<string name="tracks">Tracks</string>
|
||||
<string name="playlists">Playlists</string>
|
||||
<string name="radios">Radios</string>
|
||||
<string name="favorites">Favorites</string>
|
||||
|
||||
<string name="playback_media_controls">Media controls</string>
|
||||
@ -99,6 +100,8 @@
|
||||
<string name="track_info_details_track_bitrate">Bitrate</string>
|
||||
<string name="track_info_details_track_instance">Funkwhale instance</string>
|
||||
|
||||
<string name="radio_playback_error">There was an error while trying to play this radio</string>
|
||||
|
||||
<string name="logout_title">Sign out</string>
|
||||
<string name="logout_content">Are you sure you want to sign out of your Funkwhale instance?</string>
|
||||
|
||||
|
@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:3.5.1")
|
||||
classpath("com.android.tools.build:gradle:3.6.3")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50")
|
||||
}
|
||||
}
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,6 @@
|
||||
#Sat May 30 20:25:10 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
Loading…
x
Reference in New Issue
Block a user