No known key found for this signature in database
GPG Key ID: A78AC64694F84063
26 changed files with 403 additions and 34 deletions
-
2.editorconfig
-
2app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt
-
2app/src/main/java/com/github/apognu/otter/adapters/AlbumsAdapter.kt
-
57app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt
-
3app/src/main/java/com/github/apognu/otter/adapters/PlaylistTracksAdapter.kt
-
45app/src/main/java/com/github/apognu/otter/adapters/RadiosAdapter.kt
-
2app/src/main/java/com/github/apognu/otter/adapters/SearchAdapter.kt
-
1app/src/main/java/com/github/apognu/otter/adapters/TracksAdapter.kt
-
30app/src/main/java/com/github/apognu/otter/fragments/RadiosFragment.kt
-
20app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt
-
92app/src/main/java/com/github/apognu/otter/playback/RadioPlayer.kt
-
18app/src/main/java/com/github/apognu/otter/repositories/RadiosRepository.kt
-
1app/src/main/java/com/github/apognu/otter/utils/Data.kt
-
3app/src/main/java/com/github/apognu/otter/utils/EventBus.kt
-
11app/src/main/java/com/github/apognu/otter/utils/Extensions.kt
-
13app/src/main/java/com/github/apognu/otter/utils/Models.kt
-
7app/src/main/java/com/github/apognu/otter/utils/Util.kt
-
62app/src/main/res/layout/fragment_radios.xml
-
5app/src/main/res/layout/partial_queue.xml
-
50app/src/main/res/layout/row_radio.xml
-
3app/src/main/res/values-fr/strings.xml
-
3app/src/main/res/values/strings.xml
-
2build.gradle.kts
-
0fastlane/metadata/android/en-US/changelogs/1000016.txt
-
0fastlane/metadata/android/fr-FR/changelogs/1000016.txt
-
3gradle/wrapper/gradle-wrapper.properties
@ -0,0 +1,2 @@ |
|||
[*.{kt,kts}] |
|||
indent_size=2 |
@ -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]) |
|||
} |
|||
} |
|||
} |
@ -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)) |
|||
} |
|||
} |
|||
} |
@ -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) |
|||
} |
@ -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> |
@ -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> |
@ -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 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue