diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3c6e2e1..350d19b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -40,6 +40,7 @@
+
diff --git a/app/src/main/java/com/github/apognu/otter/activities/DownloadsActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/DownloadsActivity.kt
new file mode 100644
index 0000000..bfb71d1
--- /dev/null
+++ b/app/src/main/java/com/github/apognu/otter/activities/DownloadsActivity.kt
@@ -0,0 +1,62 @@
+package com.github.apognu.otter.activities
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.github.apognu.otter.R
+import com.github.apognu.otter.adapters.DownloadsAdapter
+import com.github.apognu.otter.utils.*
+import com.google.gson.Gson
+import kotlinx.android.synthetic.main.activity_downloads.*
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+class DownloadsActivity : AppCompatActivity() {
+ lateinit var adapter: DownloadsAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.activity_downloads)
+
+ adapter = DownloadsAdapter(this, RefreshListener()).also {
+ downloads.layoutManager = LinearLayoutManager(this)
+ downloads.adapter = it
+ }
+
+ GlobalScope.launch(Main) {
+ while (true) {
+ refresh()
+ delay(1000)
+ }
+ }
+ }
+
+ private fun refresh() {
+ GlobalScope.launch(Main) {
+ RequestBus.send(Request.GetDownloads).wait()?.let { response ->
+ adapter.downloads.clear()
+
+ while (response.cursor.moveToNext()) {
+ val download = response.cursor.download
+
+ Gson().fromJson(String(download.request.data), DownloadInfo::class.java)?.let { info ->
+ adapter.downloads.add(info.apply {
+ this.download = download
+ })
+ }
+ }
+
+ adapter.notifyDataSetChanged()
+ }
+ }
+ }
+
+ inner class RefreshListener : DownloadsAdapter.OnRefreshListener {
+ override fun refresh() {
+ this@DownloadsActivity.refresh()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt
index 2913230..01f6f8a 100644
--- a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt
+++ b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt
@@ -31,6 +31,7 @@ import com.github.apognu.otter.utils.*
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitStringResponse
import com.google.android.exoplayer2.Player
+import com.google.android.exoplayer2.offline.DownloadService
import com.google.gson.Gson
import com.preference.PowerPreference
import com.squareup.picasso.Picasso
@@ -78,6 +79,7 @@ class MainActivity : AppCompatActivity() {
super.onResume()
startService(Intent(this, PlayerService::class.java))
+ DownloadService.start(this, PinService::class.java)
now_playing_toggle.setOnClickListener {
CommandBus.send(Command.ToggleState)
@@ -163,6 +165,7 @@ class MainActivity : AppCompatActivity() {
EventBus.send(Event.ListingsChanged)
}
+ R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java))
R.id.settings -> startActivityForResult(Intent(this, SettingsActivity::class.java), 0)
}
diff --git a/app/src/main/java/com/github/apognu/otter/adapters/DownloadsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/DownloadsAdapter.kt
new file mode 100644
index 0000000..4ca065d
--- /dev/null
+++ b/app/src/main/java/com/github/apognu/otter/adapters/DownloadsAdapter.kt
@@ -0,0 +1,86 @@
+package com.github.apognu.otter.adapters
+
+import android.content.Context
+import android.graphics.drawable.Icon
+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.playback.PinService
+import com.github.apognu.otter.utils.DownloadInfo
+import com.google.android.exoplayer2.offline.Download
+import com.google.android.exoplayer2.offline.DownloadService
+import kotlinx.android.synthetic.main.row_download.view.*
+
+class DownloadsAdapter(private val context: Context, private val listener: OnRefreshListener) : RecyclerView.Adapter() {
+ interface OnRefreshListener {
+ fun refresh()
+ }
+
+ var downloads: MutableList = mutableListOf()
+
+ override fun getItemCount() = downloads.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(context).inflate(R.layout.row_download, parent, false)
+
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val download = downloads[position]
+
+ holder.title.text = download.title
+ holder.artist.text = download.artist
+
+ download.download?.let { state ->
+ when (state.isTerminalState) {
+ true -> {
+ holder.progress.visibility = View.GONE
+ holder.toggle.visibility = View.GONE
+ }
+
+ false -> {
+ holder.progress.visibility = View.VISIBLE
+ holder.toggle.visibility = View.VISIBLE
+ holder.progress.progress = state.percentDownloaded.toInt()
+
+ when (state.state) {
+ Download.STATE_REMOVING -> {
+ holder.progress.visibility = View.GONE
+ holder.toggle.visibility = View.GONE
+ }
+
+ Download.STATE_STOPPED -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.play))
+ else -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.pause))
+ }
+ }
+ }
+
+ holder.toggle.setOnClickListener {
+ if (state.state == Download.STATE_DOWNLOADING) {
+ DownloadService.sendSetStopReason(context, PinService::class.java, download.id, 1, false)
+ } else {
+ DownloadService.sendSetStopReason(context, PinService::class.java, download.id, Download.STOP_REASON_NONE, false)
+ }
+
+ listener.refresh()
+ }
+
+ holder.delete.setOnClickListener {
+ DownloadService.sendRemoveDownload(context, PinService::class.java, download.id, false)
+
+ listener.refresh()
+ }
+ }
+ }
+
+ inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val title = view.title
+ val artist = view.artist
+ val progress = view.progress
+ val toggle = view.toggle
+ val delete = view.delete
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/apognu/otter/playback/PinService.kt b/app/src/main/java/com/github/apognu/otter/playback/PinService.kt
index 3568423..901ee17 100644
--- a/app/src/main/java/com/github/apognu/otter/playback/PinService.kt
+++ b/app/src/main/java/com/github/apognu/otter/playback/PinService.kt
@@ -1,12 +1,20 @@
package com.github.apognu.otter.playback
import android.app.Notification
+import android.content.Intent
import com.github.apognu.otter.Otter
import com.github.apognu.otter.R
import com.github.apognu.otter.utils.AppContext
+import com.github.apognu.otter.utils.Request
+import com.github.apognu.otter.utils.RequestBus
+import com.github.apognu.otter.utils.Response
import com.google.android.exoplayer2.offline.*
import com.google.android.exoplayer2.scheduler.Scheduler
import com.google.android.exoplayer2.ui.DownloadNotificationHelper
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
private val manager by lazy {
@@ -17,13 +25,27 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
DownloadManager(this, DefaultDownloadIndex(database), DefaultDownloaderFactory(helper))
}
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ buildResumeDownloadsIntent(this, PinService::class.java, true)
+
+ GlobalScope.launch(Main) {
+ RequestBus.get().collect { request ->
+ when (request) {
+ is Request.GetDownloads -> request.channel?.offer(Response.Downloads(getDownloads()))
+ }
+ }
+ }
+
+ return super.onStartCommand(intent, flags, startId)
+ }
+
override fun getDownloadManager() = manager
override fun getScheduler(): Scheduler? = null
override fun getForegroundNotification(downloads: MutableList?): Notification {
- return DownloadNotificationHelper(this, AppContext.NOTIFICATION_CHANNEL_DOWNLOADS).buildProgressNotification(R.drawable.ottershape, null, null, downloads)
+ return DownloadNotificationHelper(this, AppContext.NOTIFICATION_CHANNEL_DOWNLOADS).buildProgressNotification(R.drawable.downloads, null, "Hello, world", downloads)
}
- fun getDownloads() = manager.downloadIndex.getDownloads()
+ private fun getDownloads() = manager.downloadIndex.getDownloads()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt
index 568d6dc..79eefbd 100644
--- a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt
+++ b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt
@@ -19,10 +19,10 @@ import com.github.apognu.otter.utils.*
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.offline.DownloadRequest
-import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.exoplayer2.offline.DownloadService.sendAddDownload
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
+import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
@@ -196,9 +196,17 @@ class PlayerService : Service() {
is Command.PinTrack -> {
message.track.bestUpload()?.let { upload ->
val url = mustNormalizeUrl(upload.listen_url)
-
- DownloadRequest(url, DownloadRequest.TYPE_PROGRESSIVE, Uri.parse(url), Collections.emptyList(), null, null).also {
- DownloadService.sendAddDownload(this@PlayerService, PinService::class.java, it, false)
+ val data = Gson().toJson(
+ DownloadInfo(
+ url,
+ message.track.title,
+ message.track.artist.name,
+ null
+ )
+ ).toByteArray()
+
+ DownloadRequest(url, DownloadRequest.TYPE_PROGRESSIVE, Uri.parse(url), Collections.emptyList(), null, data).also {
+ sendAddDownload(this@PlayerService, PinService::class.java, it, false)
}
}
}
diff --git a/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt
index b617e90..bc8aca6 100644
--- a/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt
+++ b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt
@@ -1,6 +1,7 @@
package com.github.apognu.otter.utils
import com.github.apognu.otter.Otter
+import com.google.android.exoplayer2.offline.DownloadCursor
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
@@ -51,12 +52,14 @@ sealed class Request(var channel: Channel? = null) {
object GetState : Request()
object GetQueue : Request()
object GetCurrentTrack : Request()
+ object GetDownloads : Request()
}
sealed class Response {
class State(val playing: Boolean) : Response()
class Queue(val queue: List