diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad6c8a3..f4af251 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -97,7 +97,7 @@ dependencies { implementation("androidx.preference:preference:1.1.0") implementation("androidx.recyclerview:recyclerview:1.0.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0") - implementation("com.google.android.material:material:1.1.0-beta01") + implementation("com.google.android.material:material:1.2.0-alpha01") implementation("com.android.support.constraint:constraint-layout:1.1.3") implementation("com.google.android.exoplayer:exoplayer:2.10.5") diff --git a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt index df14a2b..b09bd38 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt @@ -64,7 +64,7 @@ class LoginActivity : AppCompatActivity() { GlobalScope.launch(Main) { try { - val result = Fuel.post("$hostname/api/v1/token", body) + val result = Fuel.post("$hostname/api/v1/token/", body) .awaitObjectResult(gsonDeserializerOf(FwCredentials::class.java)) result.fold( 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 42727b2..f65d022 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 @@ -256,7 +256,7 @@ class MainActivity : AppCompatActivity() { .centerCrop() .into(now_playing_details_cover) - favoriteCheckRepository.fetch().untilNetwork(IO) { favorites -> + favoriteCheckRepository.fetch().untilNetwork(IO) { favorites, _ -> GlobalScope.launch(Main) { track.favorite = favorites.contains(track.id) 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 2616748..5cd6f45 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 @@ -44,7 +44,7 @@ class SearchActivity : AppCompatActivity() { adapter.data.clear() adapter.notifyDataSetChanged() - repository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks -> + repository.fetch(Repository.Origin.Network.origin).untilNetwork { tracks, _ -> search_spinner.visibility = View.GONE search_empty.visibility = View.GONE diff --git a/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt index 0ea059b..042f3d8 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/SettingsActivity.kt @@ -48,6 +48,17 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { "oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java)) + + "experiments" -> { + context?.let { context -> + AlertDialog.Builder(context) + .setTitle(context.getString(R.string.settings_experiments_restart_title)) + .setMessage(context.getString(R.string.settings_experiments_restart_content)) + .setPositiveButton(android.R.string.yes) { _, _ -> } + .show() + } + } + "logout" -> { context?.let { context -> AlertDialog.Builder(context) diff --git a/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt index e554667..f4bc6da 100644 --- a/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt +++ b/app/src/main/java/com/github/apognu/otter/adapters/BrowseTabsAdapter.kt @@ -8,11 +8,17 @@ 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.preference.PowerPreference class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { var tabs = mutableListOf() - override fun getCount() = 4 + override fun getCount(): Int { + return when (PowerPreference.getDefaultFile().getBoolean("experiments", false)) { + true -> 4 + false -> 3 + } + } override fun getItem(position: Int): Fragment { tabs.getOrNull(position)?.let { diff --git a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt index b40511a..2aeafd7 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt @@ -29,7 +29,7 @@ class AlbumsFragment : FunkwhaleFragment() { arguments = bundleOf( "artistId" to artist.id, "artistName" to artist.name, - "artistArt" to artist.albums!![0].cover.original + "artistArt" to if (artist.albums?.isNotEmpty() == true) artist.albums[0].cover.original else "" ) } } diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt index c263671..ef1c815 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt @@ -17,8 +17,6 @@ class FavoritesFragment : FunkwhaleFragment() { lateinit var favoritesRepository: FavoritesRepository - override var fetchOnCreate = false - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt index b8c6a9b..d2a446f 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt @@ -4,13 +4,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.github.apognu.otter.repositories.HttpUpstream import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.Cache import com.github.apognu.otter.utils.untilNetwork +import com.google.gson.Gson import kotlinx.android.synthetic.main.fragment_artists.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch abstract class FunkwhaleAdapter : RecyclerView.Adapter() { var data: MutableList = mutableListOf() @@ -24,7 +30,6 @@ abstract class FunkwhaleFragment> : Fragment lateinit var repository: Repository lateinit var adapter: A - open var fetchOnCreate = true private var initialFetched = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -37,56 +42,64 @@ abstract class FunkwhaleFragment> : Fragment recycler.layoutManager = layoutManager recycler.adapter = adapter - scroller?.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int -> - if (!scroller.canScrollVertically(1)) { - repository.fetch(Repository.Origin.Network.origin, adapter.data).untilNetwork { - swiper?.isRefreshing = false - - onDataFetched(it) - - adapter.data = it.toMutableList() - adapter.notifyDataSetChanged() + (repository.upstream as? HttpUpstream<*, *>)?.let { upstream -> + if (upstream.behavior == HttpUpstream.Behavior.Progressive) { + recycler.setOnScrollChangeListener { _, _, _, _, _ -> + if (!recycler.canScrollVertically(1)) { + fetch(Repository.Origin.Network.origin, adapter.data.size) + } } } } - swiper?.isRefreshing = true - - if (fetchOnCreate) fetch() + fetch() } override fun onResume() { super.onResume() - recycler.adapter = adapter - swiper?.setOnRefreshListener { - repository.fetch(Repository.Origin.Network.origin, listOf()).untilNetwork { - swiper?.isRefreshing = false - - onDataFetched(it) - - adapter.data = it.toMutableList() - adapter.notifyDataSetChanged() - } + fetch(Repository.Origin.Network.origin) } - - if (!fetchOnCreate) fetch() } open fun onDataFetched(data: List) {} - private fun fetch() { - if (!initialFetched) { - initialFetched = true + private fun fetch(upstreams: Int = (Repository.Origin.Network.origin and Repository.Origin.Cache.origin), size: Int = 0) { + var cleared = false - repository.fetch().untilNetwork { + swiper?.isRefreshing = true + + if (size == 0) { + cleared = true + adapter.data.clear() + } + + repository.fetch(upstreams, size).untilNetwork(IO) { data, hasMore -> + onDataFetched(data) + + if (!hasMore) { swiper?.isRefreshing = false - onDataFetched(it) + repository.cacheId?.let { cacheId -> + Cache.set( + context, + cacheId, + Gson().toJson(repository.cache(adapter.data)).toByteArray() + ) + } + } - adapter.data = it.toMutableList() - adapter.notifyDataSetChanged() + GlobalScope.launch(Main) { + adapter.data.addAll(data) + + when (cleared) { + true -> { + adapter.notifyDataSetChanged() + cleared = false + } + false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size) + } } } } diff --git a/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt index df9d4ed..1415d0a 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/PlaylistTracksFragment.kt @@ -99,9 +99,11 @@ class PlaylistTracksFragment : FunkwhaleFragment cover_top_left } - Picasso.get() - .maybeLoad(maybeNormalizeUrl(url)) - .into(imageView) + GlobalScope.launch(Main) { + Picasso.get() + .maybeLoad(maybeNormalizeUrl(url)) + .into(imageView) + } } } 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 17ca803..bd8b60e 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 @@ -403,7 +403,6 @@ class PlayerService : Service() { } override fun onPlayerError(error: ExoPlaybackException?) { - log(error.toString()) EventBus.send( Event.PlaybackError( getString(R.string.error_playback) diff --git a/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt index 8042ecd..9cee224 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt @@ -3,7 +3,6 @@ package com.github.apognu.otter.playback import android.content.Context import android.net.Uri import com.github.apognu.otter.R -import com.github.apognu.otter.repositories.FavoritesRepository import com.github.apognu.otter.utils.* import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.google.android.exoplayer2.source.ConcatenatingMediaSource @@ -94,8 +93,6 @@ class QueueManager(val context: Context) { val sources = tracks.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") - log(url) - ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)) } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt index fbbff60..efb6920 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/AlbumsRepository.kt @@ -17,8 +17,8 @@ class AlbumsRepository(override val context: Context?, artistId: Int? = null) : override val upstream: Upstream by lazy { val url = - if (artistId == null) "/api/v1/albums?playable=true" - else "/api/v1/albums?playable=true&artist=$artistId" + if (artistId == null) "/api/v1/albums/?playable=true" + else "/api/v1/albums/?playable=true&artist=$artistId" HttpUpstream>( HttpUpstream.Behavior.Progressive, diff --git a/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt index 3676a67..c01b0e0 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/ArtistsRepository.kt @@ -11,7 +11,7 @@ import java.io.BufferedReader class ArtistsRepository(override val context: Context?) : Repository() { override val cacheId = "artists" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/artists?playable=true", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/artists/?playable=true", object : TypeToken() {}.type) override fun cache(data: List) = ArtistsCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(ArtistsCache::class.java).deserialize(reader) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt index d29ec27..dd0c015 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt @@ -14,7 +14,7 @@ import java.io.BufferedReader class FavoritesRepository(override val context: Context?) : Repository() { override val cacheId = "favorites.v2" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?favorites=true&playable=true", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?favorites=true&playable=true", object : TypeToken() {}.type) override fun cache(data: List) = TracksCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) @@ -30,7 +30,7 @@ class FavoritesRepository(override val context: Context?) : Repository() { override val cacheId = "favorited" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.Single, "/api/v1/favorites/tracks/all?playable=true", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Single, "/api/v1/favorites/tracks/all/?playable=true", object : TypeToken() {}.type) override fun cache(data: List) = FavoritedCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(FavoritedCache::class.java).deserialize(reader) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt index dbb2c88..9b064d9 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt @@ -18,7 +18,7 @@ import java.io.Reader import java.lang.reflect.Type import kotlin.math.ceil -class HttpUpstream>(private val behavior: Behavior, private val url: String, private val type: Type) : Upstream { +class HttpUpstream>(val behavior: Behavior, private val url: String, private val type: Type) : Upstream { enum class Behavior { Single, AtOnce, Progressive } @@ -33,10 +33,10 @@ class HttpUpstream>(private val behavior: Beha return _channel!! } - override fun fetch(data: List): Channel>? { - if (behavior == Behavior.Single && data.isNotEmpty()) return null + override fun fetch(size: Int): Channel>? { + if (behavior == Behavior.Single && size != 0) return null - val page = ceil(data.size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1 + val page = ceil(size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1 GlobalScope.launch(Dispatchers.IO) { val offsetUrl = @@ -49,19 +49,22 @@ class HttpUpstream>(private val behavior: Beha get(offsetUrl).fold( { response -> - val data = data.plus(response.getData()) - - log(data.size.toString()) + val data = response.getData() if (behavior == Behavior.Progressive || response.next == null) { - channel.offer(Repository.Response(Repository.Origin.Network, data)) + channel.offer(Repository.Response(Repository.Origin.Network, data, false)) } else { - fetch(data) + channel.offer(Repository.Response(Repository.Origin.Network, data, true)) + + fetch(size + data.size) } }, { error -> + log(error.toString()) + when (error.exception) { is RefreshError -> EventBus.send(Event.LogOut) + else -> channel.offer(Repository.Response(Repository.Origin.Network, listOf(), false)) } } ) @@ -77,8 +80,6 @@ class HttpUpstream>(private val behavior: Beha } suspend fun get(url: String): Result { - log(url) - val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token") val (_, response, result) = Fuel diff --git a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt index f3ce397..b1f9e00 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistTracksRepository.kt @@ -12,7 +12,7 @@ import java.io.BufferedReader class PlaylistTracksRepository(override val context: Context?, playlistId: Int) : Repository() { override val cacheId = "tracks-playlist-$playlistId" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.Single, "/api/v1/playlists/$playlistId/tracks?playable=true", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Single, "/api/v1/playlists/$playlistId/tracks/?playable=true", object : TypeToken() {}.type) override fun cache(data: List) = PlaylistTracksCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistTracksCache::class.java).deserialize(reader) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt index 30c597a..e7b4e42 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/PlaylistsRepository.kt @@ -11,7 +11,7 @@ import java.io.BufferedReader class PlaylistsRepository(override val context: Context?) : Repository() { override val cacheId = "tracks-playlists" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/playlists?playable=true", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.Progressive, "/api/v1/playlists/?playable=true", object : TypeToken() {}.type) override fun cache(data: List) = PlaylistsCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(PlaylistsCache::class.java).deserialize(reader) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt index 8b225e0..eda8774 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt @@ -6,11 +6,13 @@ import com.github.apognu.otter.utils.CacheItem import com.github.apognu.otter.utils.untilNetwork import com.google.gson.Gson import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch import java.io.BufferedReader interface Upstream { - fun fetch(data: List = listOf()): Channel>? + fun fetch(size: Int = 0): Channel>? } abstract class Repository> { @@ -19,7 +21,7 @@ abstract class Repository> { Network(0b10) } - data class Response(val origin: Origin, val data: List) + data class Response(val origin: Origin, val data: List, val hasMore: Boolean) abstract val context: Context? abstract val cacheId: String? @@ -35,29 +37,31 @@ abstract class Repository> { return _channel!! } - protected open fun cache(data: List): C? = null + open fun cache(data: List): C? = null protected open fun uncache(reader: BufferedReader): C? = null - fun fetch(upstreams: Int = Origin.Cache.origin and Origin.Network.origin, from: List = listOf()): Channel> { + fun fetch(upstreams: Int = Origin.Cache.origin and Origin.Network.origin, size: Int = 0): Channel> { if (Origin.Cache.origin and upstreams == upstreams) fromCache() - if (Origin.Network.origin and upstreams == upstreams) fromNetwork(from) + if (Origin.Network.origin and upstreams == upstreams) fromNetwork(size) return channel } private fun fromCache() { - cacheId?.let { cacheId -> - Cache.get(context, cacheId)?.let { reader -> - uncache(reader)?.let { cache -> - channel.offer(Response(Origin.Cache, cache.data)) + GlobalScope.launch(IO) { + cacheId?.let { cacheId -> + Cache.get(context, cacheId)?.let { reader -> + uncache(reader)?.let { cache -> + channel.offer(Response(Origin.Cache, cache.data, false)) + } } } } } - private fun fromNetwork(from: List) { - upstream.fetch(data = from)?.untilNetwork(IO) { - val data = onDataFetched(it) + private fun fromNetwork(size: Int) { + upstream.fetch(size)?.untilNetwork(IO) { data, hasMore -> + val data = onDataFetched(data) cacheId?.let { cacheId -> Cache.set( @@ -67,7 +71,7 @@ abstract class Repository> { ) } - channel.offer(Response(Origin.Network, data)) + channel.offer(Response(Origin.Network, data, hasMore)) } } 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 f8cb44d..32cda6d 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 @@ -12,7 +12,7 @@ import java.io.BufferedReader class SearchRepository(override val context: Context?, query: String) : Repository() { override val cacheId: String? = null - override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?playable=true&q=$query", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&q=$query", object : TypeToken() {}.type) override fun cache(data: List) = TracksCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) 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 7ebe720..a140aea 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 @@ -12,7 +12,7 @@ import java.io.BufferedReader class TracksRepository(override val context: Context?, albumId: Int) : Repository() { override val cacheId = "tracks-album-$albumId" - override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks?playable=true&album=$albumId", object : TypeToken() {}.type) + override val upstream = HttpUpstream>(HttpUpstream.Behavior.AtOnce, "/api/v1/tracks/?playable=true&album=$albumId", object : TypeToken() {}.type) override fun cache(data: List) = TracksCache(data) override fun uncache(reader: BufferedReader) = gsonDeserializerOf(TracksCache::class.java).deserialize(reader) 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 87695d0..4c87a0b 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 @@ -3,7 +3,10 @@ package com.github.apognu.otter.utils import com.github.apognu.otter.Otter import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.filter +import kotlinx.coroutines.channels.map import kotlinx.coroutines.launch sealed class Command { diff --git a/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt index 1ddb2ed..a387b9b 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt @@ -1,6 +1,5 @@ package com.github.apognu.otter.utils -import android.annotation.SuppressLint import android.content.Context import android.os.Build import androidx.core.content.ContextCompat @@ -29,12 +28,12 @@ inline fun Channel>.await(context: CoroutineContext = } } -inline fun Channel>.untilNetwork(context: CoroutineContext = Main, crossinline callback: (data: List) -> Unit) { +inline fun Channel>.untilNetwork(context: CoroutineContext = Main, crossinline callback: (data: List, hasMore: Boolean) -> Unit) { GlobalScope.launch(context) { for (data in this@untilNetwork) { - callback(data.data) + callback(data.data, data.hasMore) - if (data.origin == Repository.Origin.Network) { + if (data.origin == Repository.Origin.Network && !data.hasMore) { close() } } diff --git a/app/src/main/res/drawable/experimental.xml b/app/src/main/res/drawable/experimental.xml new file mode 100644 index 0000000..4d83902 --- /dev/null +++ b/app/src/main/res/drawable/experimental.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_albums_grid.xml b/app/src/main/res/layout/fragment_albums_grid.xml index cfdb690..0656c17 100644 --- a/app/src/main/res/layout/fragment_albums_grid.xml +++ b/app/src/main/res/layout/fragment_albums_grid.xml @@ -1,47 +1,60 @@ - + android:layout_height="match_parent"> - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="match_parent" + tools:itemCount="10" + tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager" + tools:listitem="@layout/row_album_grid" + tools:spanCount="3" /> - + + + + + + + android:orientation="vertical"> - + - + - + - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_artists.xml b/app/src/main/res/layout/fragment_artists.xml index 8289146..0b54283 100644 --- a/app/src/main/res/layout/fragment_artists.xml +++ b/app/src/main/res/layout/fragment_artists.xml @@ -1,49 +1,59 @@ - + android:layout_height="match_parent"> - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + tools:listitem="@layout/row_artist" /> - + - + + + + + android:orientation="vertical"> - + - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml index a188845..2ab0db0 100644 --- a/app/src/main/res/layout/fragment_favorites.xml +++ b/app/src/main/res/layout/fragment_favorites.xml @@ -1,27 +1,43 @@ - - + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="match_parent" + tools:listitem="@layout/row_track" /> + + + + + + + android:clipChildren="false" + app:layout_collapseMode="parallax"> - - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playlists.xml b/app/src/main/res/layout/fragment_playlists.xml index 74046c4..a4e7cec 100644 --- a/app/src/main/res/layout/fragment_playlists.xml +++ b/app/src/main/res/layout/fragment_playlists.xml @@ -1,49 +1,62 @@ - + android:layout_height="match_parent"> - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + tools:itemCount="10" + tools:listitem="@layout/row_playlist" /> - + - + + + + + android:orientation="vertical"> - + - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c70d8e8..85aac71 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -38,6 +38,10 @@ Le mode jour sera toujours préféré Suivre les préférences du système Le mode nuit suivra les préférence système + Activer les fonctionnalité expérimentales + Utiliser à vos risques et périls, peut potentiellement ralentir ou crasher l\'application + Relancement requis + Veuillez tuer puis relancer l\'application afin que ce changement soit pris en compte Déconnexion Artistes diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b985dd..7785003 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,10 @@ Light mode will always be preferred Follow system settings Night mode will follow system settings + Enable experimental features + Use at your own risks, may freeze or crash the app + Restart required + Please kill and restart the app in order for this change to take effect Sign out Artists diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index bffd0c9..21e5ef4 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -39,6 +39,12 @@ android:key="oss_licences" android:title="@string/title_oss_licences" /> + +