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 index 80a96cf..de107e0 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/DownloadsActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/DownloadsActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import kotlinx.coroutines.flow.collect import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager +import com.github.apognu.otter.Otter import com.github.apognu.otter.R import com.github.apognu.otter.adapters.DownloadsAdapter import com.github.apognu.otter.utils.* @@ -45,21 +46,21 @@ class DownloadsActivity : AppCompatActivity() { private fun refresh() { GlobalScope.launch(Main) { - RequestBus.send(Request.GetDownloads).wait()?.let { response -> - adapter.downloads.clear() + val cursor = Otter.get().exoDownloadManager.downloadIndex.getDownloads() - while (response.cursor.moveToNext()) { - val download = response.cursor.download + adapter.downloads.clear() - download.getMetadata()?.let { info -> - adapter.downloads.add(info.apply { - this.download = download - }) - } + while (cursor.moveToNext()) { + val download = cursor.download + + download.getMetadata()?.let { info -> + adapter.downloads.add(info.apply { + this.download = download + }) } - - adapter.notifyDataSetChanged() } + + adapter.notifyDataSetChanged() } } diff --git a/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt index 5a507bf..f7f254f 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt @@ -25,6 +25,8 @@ class SearchActivity : AppCompatActivity() { lateinit var favoritesRepository: FavoritesRepository + var done = 0 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -46,6 +48,8 @@ class SearchActivity : AppCompatActivity() { search.clearFocus() rawQuery?.let { + done = 0 + val query = URLEncoder.encode(it, "UTF-8") tracksRepository = TracksSearchRepository(this@SearchActivity, query.toLowerCase(Locale.ROOT)) @@ -54,6 +58,7 @@ class SearchActivity : AppCompatActivity() { favoritesRepository = FavoritesRepository(this@SearchActivity) search_spinner.visibility = View.VISIBLE + search_empty.visibility = View.GONE search_no_results.visibility = View.GONE adapter.artists.clear() @@ -62,33 +67,24 @@ class SearchActivity : AppCompatActivity() { adapter.notifyDataSetChanged() artistsRepository.fetch(Repository.Origin.Network.origin).untilNetwork { artists, _, _ -> - when (artists.isEmpty()) { - true -> search_no_results.visibility = View.VISIBLE - false -> adapter.artists.addAll(artists) - } + done++ - adapter.notifyDataSetChanged() + adapter.artists.addAll(artists) + refresh() } albumsRepository.fetch(Repository.Origin.Network.origin).untilNetwork { albums, _, _ -> - when (albums.isEmpty()) { - true -> search_no_results.visibility = View.VISIBLE - false -> adapter.albums.addAll(albums) - } + done++ - adapter.notifyDataSetChanged() + adapter.albums.addAll(albums) + refresh() } tracksRepository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks, _, _ -> - search_spinner.visibility = View.GONE - search_empty.visibility = View.GONE + done++ - when (tracks.isEmpty()) { - true -> search_no_results.visibility = View.VISIBLE - false -> adapter.tracks.addAll(tracks) - } - - adapter.notifyDataSetChanged() + adapter.tracks.addAll(tracks) + refresh() } } @@ -99,6 +95,20 @@ class SearchActivity : AppCompatActivity() { }) } + private fun refresh() { + adapter.notifyDataSetChanged() + + if (adapter.artists.size + adapter.albums.size + adapter.tracks.size == 0) { + search_no_results.visibility = View.VISIBLE + } else { + search_no_results.visibility = View.GONE + } + + if (done == 3) { + search_spinner.visibility = View.INVISIBLE + } + } + inner class SearchResultClickListener : SearchAdapter.OnSearchResultClickListener { override fun onArtistClick(holder: View?, artist: Artist) { ArtistsFragment.openAlbums(this@SearchActivity, artist) diff --git a/app/src/main/java/com/github/apognu/otter/adapters/SearchAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/SearchAdapter.kt index 1b2925b..48c8d76 100644 --- a/app/src/main/java/com/github/apognu/otter/adapters/SearchAdapter.kt +++ b/app/src/main/java/com/github/apognu/otter/adapters/SearchAdapter.kt @@ -2,6 +2,8 @@ package com.github.apognu.otter.adapters import android.annotation.SuppressLint import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.Typeface import android.os.Build import android.view.Gravity @@ -69,10 +71,6 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc 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) @@ -93,27 +91,33 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc if (position == 0) { holder.title.text = context.getString(R.string.artists) holder.itemView.visibility = View.VISIBLE + holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) if (artists.isEmpty()) { holder.itemView.visibility = View.GONE + holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0) } } if (position == (artists.size + 1)) { holder.title.text = context.getString(R.string.albums) holder.itemView.visibility = View.VISIBLE + holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) if (albums.isEmpty()) { holder.itemView.visibility = View.GONE + holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0) } } if (position == (artists.size + albums.size + 2)) { holder.title.text = context.getString(R.string.tracks) holder.itemView.visibility = View.VISIBLE + holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) if (tracks.isEmpty()) { holder.itemView.visibility = View.GONE + holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0) } } } @@ -160,6 +164,8 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc holder.artist.typeface = Typeface.create(holder.artist.typeface, Typeface.NORMAL) }) + holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + if (resultType == ResultType.Track.ordinal) { (item as? Track)?.let { track -> context?.let { context -> @@ -183,6 +189,23 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc } } + when (track.cached || track.downloaded) { + true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0) + false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } + + if (track.cached && !track.downloaded) { + holder.title.compoundDrawables.forEach { + it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN) + } + } + + if (track.downloaded) { + holder.title.compoundDrawables.forEach { + it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN) + } + } + holder.actions.setOnClickListener { PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { inflate(R.menu.row_track) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt index 6f96dc3..14c4538 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/SearchRepository.kt @@ -1,6 +1,7 @@ package com.github.apognu.otter.repositories import android.content.Context +import com.github.apognu.otter.Otter import com.github.apognu.otter.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.gson.reflect.TypeToken @@ -22,8 +23,18 @@ class TracksSearchRepository(override val context: Context?, query: String) : Re .toList() .flatten() + val downloaded = TracksRepository.getDownloadedIds() ?: listOf() + data.map { track -> track.favorite = favorites.contains(track.id) + track.downloaded = downloaded.contains(track.id) + + track.bestUpload()?.let { upload -> + val url = mustNormalizeUrl(upload.listen_url) + + track.cached = Otter.get().exoCache.isCached(url, 0, upload.duration * 1000L) + } + track } } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt index 1f58878..a29191c 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/TracksRepository.kt @@ -19,22 +19,21 @@ class TracksRepository(override val context: Context?, albumId: Int) : Repositor override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) companion object { - suspend fun getDownloadedIds(): List? { - return RequestBus.send(Request.GetDownloads).wait()?.let { response -> - val ids: MutableList = mutableListOf() + fun getDownloadedIds(): List? { + val cursor = Otter.get().exoDownloadManager.downloadIndex.getDownloads() + val ids: MutableList = mutableListOf() - while (response.cursor.moveToNext()) { - val download = response.cursor.download + while (cursor.moveToNext()) { + val download = cursor.download - download.getMetadata()?.let { - if (download.state == Download.STATE_COMPLETED) { - ids.add(it.id) - } + download.getMetadata()?.let { + if (download.state == Download.STATE_COMPLETED) { + ids.add(it.id) } } - - ids } + + return ids } } 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 c5c35d6..1438594 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 @@ -4,7 +4,6 @@ import com.github.apognu.otter.Otter import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.DownloadCursor import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.asFlow @@ -89,7 +88,7 @@ object CommandBus { object RequestBus { fun send(request: Request): Channel { return Channel().also { - GlobalScope.launch(Main) { + GlobalScope.launch(IO) { request.channel = it Otter.get().requestBus.offer(request) diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index a805dd9..d4f4073 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -18,28 +18,39 @@ - + android:orientation="vertical"> + + + + + + - -