diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 92b9b17..cd3d4b2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,7 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.google.android.material:material:1.3.0-alpha02") implementation("com.android.support.constraint:constraint-layout:2.0.1") + implementation("com.google.android:flexbox:2.0.1") implementation("com.google.android.exoplayer:exoplayer-core:2.11.5") implementation("com.google.android.exoplayer:exoplayer-ui:2.11.5") diff --git a/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt index 7a17a55..9fef631 100644 --- a/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt +++ b/app/src/main/java/com/github/apognu/otter/adapters/AlbumsGridAdapter.kt @@ -37,7 +37,7 @@ class AlbumsGridAdapter(val context: Context?, private val listener: OnAlbumClic Picasso.get() .maybeLoad(maybeNormalizeUrl(album.cover())) .fit() - .placeholder(R.drawable.cover) + .placeholder(R.drawable.cover).placeholder(R.drawable.cover) .transform(RoundedCornersTransformation(16, 0)) .into(holder.cover) 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 3f5fe75..2480d7d 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 @@ -9,7 +9,7 @@ import com.github.apognu.otter.fragments.* class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { var tabs = mutableListOf() - override fun getCount() = 5 + override fun getCount() = 6 override fun getItem(position: Int): Fragment { tabs.getOrNull(position)?.let { @@ -17,11 +17,12 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm } val fragment = when (position) { - 0 -> ArtistsFragment() - 1 -> AlbumsGridFragment() - 2 -> PlaylistsFragment() - 3 -> RadiosFragment() - 4 -> FavoritesFragment() + 0 -> HomeFragment() + 1 -> ArtistsFragment() + 2 -> AlbumsGridFragment() + 3 -> PlaylistsFragment() + 4 -> RadiosFragment() + 5 -> FavoritesFragment() else -> ArtistsFragment() } @@ -32,11 +33,12 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : Fragm override fun getPageTitle(position: Int): String { return when (position) { - 0 -> context.getString(R.string.artists) - 1 -> context.getString(R.string.albums) - 2 -> context.getString(R.string.playlists) - 3 -> context.getString(R.string.radios) - 4 -> context.getString(R.string.favorites) + 0 -> "Otter" + 1 -> context.getString(R.string.artists) + 2 -> context.getString(R.string.albums) + 3 -> context.getString(R.string.playlists) + 4 -> context.getString(R.string.radios) + 5 -> context.getString(R.string.favorites) else -> "" } } diff --git a/app/src/main/java/com/github/apognu/otter/adapters/home/DummyAdapter.kt b/app/src/main/java/com/github/apognu/otter/adapters/home/DummyAdapter.kt new file mode 100644 index 0000000..e6461d3 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/adapters/home/DummyAdapter.kt @@ -0,0 +1,47 @@ +package com.github.apognu.otter.adapters.home + +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.utils.mustNormalizeUrl +import com.squareup.picasso.Picasso +import jp.wasabeef.picasso.transformations.RoundedCornersTransformation +import kotlinx.android.synthetic.main.row_dummy.view.* + +class DummyAdapter(val context: Context?, val viewRes: Int = R.layout.row_dummy) : RecyclerView.Adapter() { + data class DummyItem(val label: String, val cover: String?) + + var data: List = listOf() + + override fun getItemCount() = data.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return LayoutInflater.from(context).inflate(viewRes, parent, false).run { + ViewHolder(this) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + data[position].also { + holder.label.text = it.label + + it.cover?.let { cover -> + Picasso + .get() + .load(mustNormalizeUrl(cover)) + .fit() + .placeholder(R.drawable.cover).placeholder(R.drawable.cover) + .transform(RoundedCornersTransformation(16, 0)) + .into(holder.cover) + } + } + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val label = view.label + val cover = view.cover + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/fragments/HomeFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/HomeFragment.kt new file mode 100644 index 0000000..2a8adaf --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/fragments/HomeFragment.kt @@ -0,0 +1,105 @@ +package com.github.apognu.otter.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.apognu.otter.R +import com.github.apognu.otter.adapters.home.DummyAdapter +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.repositories.home.RecentlyAddedRepository +import com.github.apognu.otter.repositories.home.RecentlyListenedRepository +import com.github.apognu.otter.repositories.home.TagsRepository +import com.github.apognu.otter.utils.untilNetwork +import com.google.android.flexbox.FlexboxLayoutManager +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class HomeFragment : Fragment() { + private lateinit var tagsRepository: TagsRepository + private lateinit var recentlyAddedRepository: RecentlyAddedRepository + private lateinit var recentlyListenedRepository: RecentlyListenedRepository + + private lateinit var tagsAdapter: DummyAdapter + private lateinit var recentlyAddedAdapter: DummyAdapter + private lateinit var recentlyListenedAdapter: DummyAdapter + private lateinit var dummyAdapter: DummyAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + tagsRepository = TagsRepository(context) + recentlyAddedRepository = RecentlyAddedRepository(context) + recentlyListenedRepository = RecentlyListenedRepository(context) + + tagsAdapter = DummyAdapter(context, R.layout.row_tag) + recentlyAddedAdapter = DummyAdapter(context) + recentlyListenedAdapter = DummyAdapter(context) + dummyAdapter = DummyAdapter(context) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_home, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + tags.apply { + adapter = tagsAdapter + layoutManager = FlexboxLayoutManager(context).apply { + isNestedScrollingEnabled = false + } + } + + random.apply { + adapter = dummyAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } + + recently_listened.apply { + adapter = recentlyListenedAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } + + recently_added.apply { + adapter = recentlyAddedAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } + + playlists.apply { + adapter = dummyAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } + + refresh() + } + + private fun refresh() { + tagsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(IO) {data, _, _ -> + GlobalScope.launch(Main) { + tagsAdapter.data = data.map { DummyAdapter.DummyItem(it.name, null) } + tagsAdapter.notifyDataSetChanged() + } + } + + recentlyListenedRepository.fetch(Repository.Origin.Network.origin).untilNetwork(IO) { data, _, _ -> + GlobalScope.launch(Main) { + recentlyListenedAdapter.data = data.map { DummyAdapter.DummyItem(it.track.title, it.track.album.cover.original) } + recentlyListenedAdapter.notifyDataSetChanged() + } + } + + recentlyAddedRepository.fetch(Repository.Origin.Network.origin).untilNetwork(IO) { data, _, _ -> + GlobalScope.launch(Main) { + recentlyAddedAdapter.data = data.map { DummyAdapter.DummyItem(it.title, it.album.cover.original) } + recentlyAddedAdapter.notifyDataSetChanged() + } + } + } +} \ No newline at end of file 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 e36fdcf..460ec33 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>(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, private val maxSize: Int = AppContext.PAGE_SIZE) : Upstream { enum class Behavior { Single, AtOnce, Progressive } @@ -26,12 +26,12 @@ class HttpUpstream>(val behavior: Behavior, privat override fun fetch(size: Int): Flow> = flow { if (behavior == Behavior.Single && size != 0) return@flow - val page = ceil(size / AppContext.PAGE_SIZE.toDouble()).toInt() + 1 + val page = ceil(size / maxSize.toDouble()).toInt() + 1 val url = Uri.parse(url) .buildUpon() - .appendQueryParameter("page_size", AppContext.PAGE_SIZE.toString()) + .appendQueryParameter("page_size", maxSize.toString()) .appendQueryParameter("page", page.toString()) .appendQueryParameter("scope", Settings.getScope()) .build() diff --git a/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyAddedRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyAddedRepository.kt new file mode 100644 index 0000000..2f1e503 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyAddedRepository.kt @@ -0,0 +1,19 @@ +package com.github.apognu.otter.repositories.home + +import android.content.Context +import com.github.apognu.otter.repositories.HttpUpstream +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.* +import com.google.gson.reflect.TypeToken + +class RecentlyAddedRepository(override val context: Context?) : Repository() { + override val cacheId = "home-recently-added" + + override val upstream = + HttpUpstream>( + HttpUpstream.Behavior.Single, + "/api/v1/tracks/?playable=true&ordering=-creation_date", + object : TypeToken() {}.type, + 10 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyListenedRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyListenedRepository.kt new file mode 100644 index 0000000..32914ea --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/home/RecentlyListenedRepository.kt @@ -0,0 +1,19 @@ +package com.github.apognu.otter.repositories.home + +import android.content.Context +import com.github.apognu.otter.repositories.HttpUpstream +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.* +import com.google.gson.reflect.TypeToken + +class RecentlyListenedRepository(override val context: Context?) : Repository() { + override val cacheId = "home-recently-listened" + + override val upstream = + HttpUpstream>( + HttpUpstream.Behavior.Single, + "/api/v1/history/listenings/?playable=true", + object : TypeToken() {}.type, + 10 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/repositories/home/TagsRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/home/TagsRepository.kt new file mode 100644 index 0000000..9c47c61 --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/repositories/home/TagsRepository.kt @@ -0,0 +1,18 @@ +package com.github.apognu.otter.repositories.home + +import android.content.Context +import com.github.apognu.otter.repositories.HttpUpstream +import com.github.apognu.otter.repositories.Repository +import com.github.apognu.otter.utils.* +import com.google.gson.reflect.TypeToken + +class TagsRepository(override val context: Context?) : Repository() { + override val cacheId = "tags" + + override val upstream = + HttpUpstream>( + HttpUpstream.Behavior.Single, + "/api/v1/tags/", + object : TypeToken() {}.type + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/apognu/otter/utils/Models.kt b/app/src/main/java/com/github/apognu/otter/utils/Models.kt index d95f5e6..e1961fd 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Models.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Models.kt @@ -16,6 +16,7 @@ class PlaylistTracksCache(data: List) : CacheItem( class RadiosCache(data: List) : CacheItem(data) class FavoritedCache(data: List) : CacheItem(data) class QueueCache(data: List) : CacheItem(data) +class TagsCache(data: List) : CacheItem(data) abstract class OtterResponse { abstract val count: Int @@ -59,6 +60,10 @@ data class RadiosResponse(override val count: Int, override val next: String?, v data class Covers(val urls: CoverUrls) data class CoverUrls(val original: String) +data class TagsResponse(override val count: Int, override val next: String?, val results: List) : OtterResponse() { + override fun getData() = results +} + typealias AlbumList = List interface SearchResult { @@ -176,4 +181,6 @@ data class DownloadInfo( val title: String, val artist: String, var download: Download? -) \ No newline at end of file +) + +data class Tag(val name: String) diff --git a/app/src/main/res/drawable/rounded.xml b/app/src/main/res/drawable/rounded.xml new file mode 100644 index 0000000..8cafec3 --- /dev/null +++ b/app/src/main/res/drawable/rounded.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000..0ea94e1 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_dummy.xml b/app/src/main/res/layout/row_dummy.xml new file mode 100644 index 0000000..6f53026 --- /dev/null +++ b/app/src/main/res/layout/row_dummy.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_tag.xml b/app/src/main/res/layout/row_tag.xml new file mode 100644 index 0000000..fe9b1a6 --- /dev/null +++ b/app/src/main/res/layout/row_tag.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file