Antoine POPINEAU
4 years ago
9 changed files with 211 additions and 6 deletions
@ -0,0 +1,94 @@ |
|||||||
|
package com.github.apognu.otter.fragments |
||||||
|
|
||||||
|
import android.app.Activity |
||||||
|
import android.app.AlertDialog |
||||||
|
import android.view.View |
||||||
|
import androidx.core.widget.addTextChangedListener |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import com.github.apognu.otter.R |
||||||
|
import com.github.apognu.otter.adapters.PlaylistsAdapter |
||||||
|
import com.github.apognu.otter.repositories.ManagementPlaylistsRepository |
||||||
|
import com.github.apognu.otter.utils.* |
||||||
|
import com.google.gson.Gson |
||||||
|
import kotlinx.android.synthetic.main.dialog_add_to_playlist.* |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.Dispatchers.IO |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
|
||||||
|
object AddToPlaylistDialog { |
||||||
|
fun show(activity: Activity, lifecycleScope: CoroutineScope, track: Track) { |
||||||
|
val dialog = AlertDialog.Builder(activity).run { |
||||||
|
setTitle("Add track to playlist") |
||||||
|
setView(activity.layoutInflater.inflate(R.layout.dialog_add_to_playlist, null)) |
||||||
|
|
||||||
|
create() |
||||||
|
} |
||||||
|
|
||||||
|
dialog.show() |
||||||
|
|
||||||
|
val repository = ManagementPlaylistsRepository(activity) |
||||||
|
|
||||||
|
dialog.name.editText?.addTextChangedListener { |
||||||
|
dialog.create.isEnabled = !(dialog.name.editText?.text?.trim()?.isBlank() ?: true) |
||||||
|
} |
||||||
|
|
||||||
|
dialog.create.setOnClickListener { |
||||||
|
val name = dialog.name.editText?.text?.toString()?.trim() ?: "" |
||||||
|
|
||||||
|
if (name.isEmpty()) return@setOnClickListener |
||||||
|
|
||||||
|
lifecycleScope.launch(IO) { |
||||||
|
repository.new(name)?.let { id -> |
||||||
|
repository.add(id, track) |
||||||
|
dialog.dismiss() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val adapter = PlaylistsAdapter(activity, object : PlaylistsAdapter.OnPlaylistClickListener { |
||||||
|
override fun onClick(holder: View?, playlist: Playlist) { |
||||||
|
repository.add(playlist.id, track) |
||||||
|
dialog.dismiss() |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
dialog.playlists.layoutManager = LinearLayoutManager(activity) |
||||||
|
dialog.playlists.adapter = adapter |
||||||
|
|
||||||
|
repository.apply { |
||||||
|
var first = true |
||||||
|
|
||||||
|
fetch().untilNetwork(lifecycleScope) { data, isCache, _, hasMore -> |
||||||
|
if (isCache) { |
||||||
|
adapter.data = data.toMutableList() |
||||||
|
adapter.notifyDataSetChanged() |
||||||
|
|
||||||
|
return@untilNetwork |
||||||
|
} |
||||||
|
|
||||||
|
if (first) { |
||||||
|
adapter.data.clear() |
||||||
|
first = false |
||||||
|
} |
||||||
|
|
||||||
|
adapter.data.addAll(data) |
||||||
|
|
||||||
|
lifecycleScope.launch(IO) { |
||||||
|
try { |
||||||
|
Cache.set( |
||||||
|
context, |
||||||
|
cacheId, |
||||||
|
Gson().toJson(cache(adapter.data)).toByteArray() |
||||||
|
) |
||||||
|
} catch (e: ConcurrentModificationException) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!hasMore) { |
||||||
|
adapter.notifyDataSetChanged() |
||||||
|
first = false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,18 +1,67 @@ |
|||||||
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.OtterResponse |
import com.github.apognu.otter.utils.* |
||||||
import com.github.apognu.otter.utils.Playlist |
import com.github.kittinunf.fuel.Fuel |
||||||
import com.github.apognu.otter.utils.PlaylistsCache |
import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult |
||||||
import com.github.apognu.otter.utils.PlaylistsResponse |
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult |
||||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf |
import com.github.kittinunf.fuel.gson.gsonDeserializerOf |
||||||
|
import com.google.gson.Gson |
||||||
import com.google.gson.reflect.TypeToken |
import com.google.gson.reflect.TypeToken |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.launch |
||||||
import java.io.BufferedReader |
import java.io.BufferedReader |
||||||
|
|
||||||
|
data class PlaylistAdd(val tracks: List<Int>, val allow_duplicates: Boolean) |
||||||
|
|
||||||
class PlaylistsRepository(override val context: Context?) : Repository<Playlist, PlaylistsCache>() { |
class PlaylistsRepository(override val context: Context?) : Repository<Playlist, PlaylistsCache>() { |
||||||
override val cacheId = "tracks-playlists" |
override val cacheId = "tracks-playlists" |
||||||
override val upstream = HttpUpstream<Playlist, OtterResponse<Playlist>>(HttpUpstream.Behavior.Progressive, "/api/v1/playlists/?playable=true&ordering=name", object : TypeToken<PlaylistsResponse>() {}.type) |
override val upstream = HttpUpstream<Playlist, OtterResponse<Playlist>>(HttpUpstream.Behavior.Progressive, "/api/v1/playlists/?playable=true&ordering=name", object : TypeToken<PlaylistsResponse>() {}.type) |
||||||
|
|
||||||
override fun cache(data: List<Playlist>) = PlaylistsCache(data) |
override fun cache(data: List<Playlist>) = PlaylistsCache(data) |
||||||
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistsCache::class.java).deserialize(reader) |
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistsCache::class.java).deserialize(reader) |
||||||
|
} |
||||||
|
|
||||||
|
class ManagementPlaylistsRepository(override val context: Context?) : Repository<Playlist, PlaylistsCache>() { |
||||||
|
override val cacheId = "tracks-playlists-management" |
||||||
|
override val upstream = HttpUpstream<Playlist, OtterResponse<Playlist>>(HttpUpstream.Behavior.AtOnce, "/api/v1/playlists/?ordering=name", object : TypeToken<PlaylistsResponse>() {}.type) |
||||||
|
|
||||||
|
override fun cache(data: List<Playlist>) = PlaylistsCache(data) |
||||||
|
override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistsCache::class.java).deserialize(reader) |
||||||
|
|
||||||
|
suspend fun new(name: String): Int? { |
||||||
|
val body = mapOf("name" to name, "privacy_level" to "me") |
||||||
|
|
||||||
|
val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/")).apply { |
||||||
|
if (!Settings.isAnonymous()) { |
||||||
|
header("Authorization", "Bearer ${Settings.getAccessToken()}") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val (_, response, result) = request |
||||||
|
.header("Content-Type", "application/json") |
||||||
|
.body(Gson().toJson(body)) |
||||||
|
.awaitObjectResponseResult(gsonDeserializerOf(Playlist::class.java)) |
||||||
|
|
||||||
|
if (response.statusCode != 201) return null |
||||||
|
|
||||||
|
return result.get().id |
||||||
|
} |
||||||
|
|
||||||
|
fun add(id: Int, track: Track) { |
||||||
|
val body = PlaylistAdd(listOf(track.id), false) |
||||||
|
|
||||||
|
val request = Fuel.post(mustNormalizeUrl("/api/v1/playlists/${id}/add/")).apply { |
||||||
|
if (!Settings.isAnonymous()) { |
||||||
|
header("Authorization", "Bearer ${Settings.getAccessToken()}") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
scope.launch(Dispatchers.IO) { |
||||||
|
request |
||||||
|
.header("Content-Type", "application/json") |
||||||
|
.body(Gson().toJson(body)) |
||||||
|
.awaitByteArrayResponseResult() |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
@ -0,0 +1,46 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<LinearLayout 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" |
||||||
|
android:orientation="vertical"> |
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout |
||||||
|
android:id="@+id/name" |
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginHorizontal="8dp" |
||||||
|
android:layout_marginTop="16dp" |
||||||
|
android:hint="New playlist..." |
||||||
|
app:boxStrokeColor="@color/controlForeground" |
||||||
|
app:hintTextColor="@color/controlForeground" |
||||||
|
app:placeholderTextColor="@color/controlForeground"> |
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" /> |
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout> |
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton |
||||||
|
android:id="@+id/create" |
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="end" |
||||||
|
android:layout_marginHorizontal="8dp" |
||||||
|
android:enabled="false" |
||||||
|
android:text="Create playlist" |
||||||
|
android:textColor="@color/controlForeground" |
||||||
|
app:rippleColor="@color/ripple" /> |
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView |
||||||
|
android:id="@+id/playlists" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
tools:itemCount="10" |
||||||
|
tools:listitem="@layout/row_playlist" /> |
||||||
|
|
||||||
|
</LinearLayout> |
Loading…
Reference in new issue