Added suggestions & Updated libs.

This commit is contained in:
Hemanth S 2020-07-14 02:09:47 +05:30
parent 865e13a536
commit 547e49507e
20 changed files with 568 additions and 186 deletions

View file

@ -133,7 +133,7 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0-alpha01' implementation 'com.google.android.material:material:1.3.0-alpha01'
def retrofit_version = '2.8.1' def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
@ -176,7 +176,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation 'com.google.android.play:core:1.7.2' implementation 'com.google.android.play:core:1.7.3'
implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'me.jorgecastillo:androidcolorx:0.2.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4' debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'com.github.dhaval2404:imagepicker:1.7.1'

View file

@ -116,6 +116,7 @@ const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
const val LAST_CHANGELOG_VERSION = "last_changelog_version" const val LAST_CHANGELOG_VERSION = "last_changelog_version"
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy" const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
const val START_DIRECTORY = "start_directory" const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen" const val LOCK_SCREEN = "lock_screen"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order" const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position" const val LYRICS_OPTIONS = "lyrics_tab_position"

View file

@ -4,24 +4,25 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.IntDef import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumFullWidthAdapter import code.name.monkey.retromusic.adapter.album.AlbumFullWidthAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Home
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
class HomeAdapter( class HomeAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
@ -40,6 +41,15 @@ class HomeAdapter(
return when (viewType) { return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
PLAYLISTS -> PlaylistViewHolder(layout) PLAYLISTS -> PlaylistViewHolder(layout)
SUGGESTIONS -> {
SuggestionsViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_suggestions,
parent,
false
)
)
}
else -> { else -> {
AlbumViewHolder( AlbumViewHolder(
LayoutInflater.from(activity).inflate( LayoutInflater.from(activity).inflate(
@ -56,23 +66,41 @@ class HomeAdapter(
when (getItemViewType(position)) { when (getItemViewType(position)) {
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.recent_albums) viewHolder.bindView(
list[position].arrayList as List<Album>,
R.string.recent_albums
)
} }
TOP_ALBUMS -> { TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.top_albums) viewHolder.bindView(
list[position].arrayList as List<Album>,
R.string.top_albums
)
} }
RECENT_ARTISTS -> { RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.recent_artists) viewHolder.bindView(
list[position].arrayList as List<Artist>,
R.string.recent_artists
)
} }
TOP_ARTISTS -> { TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.top_artists) viewHolder.bindView(list[position].arrayList as List<Artist>, R.string.top_artists)
} }
PLAYLISTS -> { PLAYLISTS -> {
val viewHolder = holder as PlaylistViewHolder val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(list[position].arrayList.toPlaylist(), R.string.favorites) viewHolder.bindView(
list[position].arrayList as List<Playlist>,
R.string.favorites
)
}
SUGGESTIONS -> {
val viewHolder = holder as SuggestionsViewHolder
viewHolder.bindView(
list[position].arrayList as List<Song>
)
} }
} }
} }
@ -88,7 +116,7 @@ class HomeAdapter(
companion object { companion object {
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS) @IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS, SUGGESTIONS)
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
annotation class HomeSection annotation class HomeSection
@ -96,11 +124,12 @@ class HomeAdapter(
const val TOP_ALBUMS = 1 const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2 const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 0 const val TOP_ARTISTS = 0
const val PLAYLISTS = 4 const val SUGGESTIONS = 4
const val PLAYLISTS = 5
} }
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) { private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Album>, titleRes: Int) { fun bindView(list: List<Album>, titleRes: Int) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
recyclerView.apply { recyclerView.apply {
show() show()
@ -112,7 +141,7 @@ class HomeAdapter(
} }
inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) { inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Artist>, titleRes: Int) { fun bindView(list: List<Artist>, titleRes: Int) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
val artistAdapter = ArtistAdapter( val artistAdapter = ArtistAdapter(
@ -131,8 +160,37 @@ class HomeAdapter(
} }
} }
private inner class SuggestionsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val images = listOf(
R.id.image1,
R.id.image2,
R.id.image3,
R.id.image4,
R.id.image5,
R.id.image6,
R.id.image7,
R.id.image8
)
fun bindView(arrayList: List<Song>) {
val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.text).setTextColor(color)
images.forEachIndexed { index, i ->
itemView.findViewById<View>(i).setOnClickListener {
MusicPlayerRemote.playNext(arrayList[index])
}
SongGlideRequest.Builder.from(Glide.with(activity), arrayList[index])
.asBitmap()
.build()
.into(itemView.findViewById(i))
}
}
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) { private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(arrayList: ArrayList<Playlist>, titleRes: Int) { fun bindView(arrayList: List<Playlist>, titleRes: Int) {
if (arrayList.isNotEmpty()) { if (arrayList.isNotEmpty()) {
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0]) val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0])
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
@ -155,27 +213,3 @@ class HomeAdapter(
val title: AppCompatTextView = itemView.findViewById(R.id.title) val title: AppCompatTextView = itemView.findViewById(R.id.title)
} }
} }
private fun <E> ArrayList<E>.toAlbums(): ArrayList<Album> {
val arrayList = ArrayList<Album>()
for (x in this) {
arrayList.add(x as Album)
}
return arrayList
}
private fun <E> ArrayList<E>.toArtists(): ArrayList<Artist> {
val arrayList = ArrayList<Artist>()
for (x in this) {
arrayList.add(x as Artist)
}
return arrayList
}
private fun <E> ArrayList<E>.toPlaylist(): ArrayList<Playlist> {
val arrayList = ArrayList<Playlist>()
for (x in this) {
arrayList.add(x as Playlist)
}
return arrayList
}

View file

@ -55,7 +55,8 @@ class LibraryViewModel(application: Application) :
_repository.topAlbums(), _repository.topAlbums(),
_repository.recentArtists(), _repository.recentArtists(),
_repository.recentAlbums(), _repository.recentAlbums(),
_repository.favoritePlaylist() _repository.favoritePlaylist(),
_repository.suggestions()
) )
for (r in result) { for (r in result) {
if (r != null) { if (r != null) {

View file

@ -55,13 +55,6 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
) )
} }
private fun loadImageFromStorage() {
UserProfileGlideRequest.Builder.from(
Glide.with(requireActivity()),
UserProfileGlideRequest.getUserModel()
).build().into(userImage)
}
private val displayMetrics: DisplayMetrics private val displayMetrics: DisplayMetrics
get() { get() {
val display = mainActivity.windowManager.defaultDisplay val display = mainActivity.windowManager.defaultDisplay
@ -111,8 +104,7 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
) )
NavigationUtil.goToUserInfo(requireActivity(), options) NavigationUtil.goToUserInfo(requireActivity(), options)
} }
titleWelcome?.text = titleWelcome?.text = String.format("%s", PreferenceUtil.userName)
String.format("%s", PreferenceUtil.userName)
homeAdapter = HomeAdapter(mainActivity, displayMetrics) homeAdapter = HomeAdapter(mainActivity, displayMetrics)
recyclerView.apply { recyclerView.apply {

View file

@ -279,6 +279,10 @@ class GradientPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelpe
updateQueuePosition() updateQueuePosition()
} }
override fun onQueueChanged() {
super.onQueueChanged()
updateLabel()
}
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
title.text = song.title title.text = song.title

View file

@ -17,14 +17,18 @@ package code.name.monkey.retromusic.loaders
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.provider.BaseColumns import android.provider.BaseColumns
import android.provider.MediaStore
import code.name.monkey.retromusic.Constants.NUMBER_OF_TOP_TRACKS import code.name.monkey.retromusic.Constants.NUMBER_OF_TOP_TRACKS
import code.name.monkey.retromusic.loaders.SongLoader.makeSongCursor
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.HistoryStore import code.name.monkey.retromusic.providers.HistoryStore
import code.name.monkey.retromusic.providers.SongPlayCountStore import code.name.monkey.retromusic.providers.SongPlayCountStore
import code.name.monkey.retromusic.util.PreferenceUtil
import java.util.* import java.util.*
/** /**
* Created by hemanths on 16/08/17. * Created by hemanths on 16/08/17.
*/ */
@ -39,19 +43,23 @@ object TopAndRecentlyPlayedTracksLoader {
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context)) return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
} }
private fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? { fun getNotRecentlyPlayedTracks(context: Context): ArrayList<Song> {
val retCursor = makeRecentTracksCursorImpl(context) val allSongs = SongLoader.getSongs(
makeSongCursor(
// clean up the databases with any ids not found context,
if (retCursor != null) { null, null,
val missingIds = retCursor.missingIds MediaStore.Audio.Media.DATE_ADDED + " ASC"
if (missingIds != null && missingIds.size > 0) { )
for (id in missingIds) { )
HistoryStore.getInstance(context).removeSongId(id) val playedSongs = SongLoader.getSongs(
} makePlayedTracksCursorAndClearUpDatabase(context)
} )
} val notRecentlyPlayedSongs = SongLoader.getSongs(
return retCursor makeNotRecentTracksCursorAndClearUpDatabase(context)
)
allSongs.removeAll(playedSongs)
allSongs.addAll(notRecentlyPlayedSongs)
return allSongs
} }
private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? { private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? {
@ -72,21 +80,19 @@ object TopAndRecentlyPlayedTracksLoader {
private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? { private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? {
// first get the top results ids from the internal database // first get the top results ids from the internal database
val songs = HistoryStore.getInstance(context).queryRecentIds() val songs = HistoryStore.getInstance(context).queryRecentIds()
songs.use {
try {
return makeSortedCursor( return makeSortedCursor(
context, songs, context,
songs!!.getColumnIndex(HistoryStore.RecentStoreColumns.ID) it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
) )
} finally {
songs?.close()
} }
} }
private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? { private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? {
// first get the top results ids from the internal database // first get the top results ids from the internal database
val songs = SongPlayCountStore.getInstance(context) val songs =
.getTopPlayedResults(NUMBER_OF_TOP_TRACKS) SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
songs.use { localSongs -> songs.use { localSongs ->
return makeSortedCursor( return makeSortedCursor(
@ -145,4 +151,57 @@ object TopAndRecentlyPlayedTracksLoader {
fun getTopArtists(context: Context): ArrayList<Artist> { fun getTopArtists(context: Context): ArrayList<Artist> {
return ArtistLoader.splitIntoArtists(getTopAlbums(context)) return ArtistLoader.splitIntoArtists(getTopAlbums(context))
} }
fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, false)
}
fun makePlayedTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, true, false)
}
fun makeNotRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, true)
}
private fun makeRecentTracksCursorAndClearUpDatabaseImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
val retCursor = makeRecentTracksCursorImpl(context, ignoreCutoffTime, reverseOrder)
// clean up the databases with any ids not found
// clean up the databases with any ids not found
if (retCursor != null) {
val missingIds = retCursor.missingIds
if (missingIds != null && missingIds.size > 0) {
for (id in missingIds) {
HistoryStore.getInstance(context).removeSongId(id)
}
}
}
return retCursor
}
private fun makeRecentTracksCursorImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
val cutoff =
(if (ignoreCutoffTime) 0 else PreferenceUtil.getRecentlyPlayedCutoffTimeMillis()).toLong()
val songs =
HistoryStore.getInstance(context).queryRecentIds(cutoff * if (reverseOrder) -1 else 1)
return songs.use {
makeSortedCursor(
context,
it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
)
}
}
} }

View file

@ -15,13 +15,10 @@
package code.name.monkey.retromusic.model package code.name.monkey.retromusic.model
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import code.name.monkey.retromusic.adapter.HomeAdapter.Companion.HomeSection import code.name.monkey.retromusic.adapter.HomeAdapter.Companion.HomeSection
class Home( class Home(
val priority: Int, val arrayList: List<*>,
@StringRes val title: Int,
val arrayList: ArrayList<*>,
@HomeSection @HomeSection
val homeSection: Int, val homeSection: Int,
@DrawableRes @DrawableRes

View file

@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
import code.name.monkey.retromusic.util.MusicUtil;
public class Playlist implements Parcelable { public class Playlist implements Parcelable {
@ -106,5 +107,14 @@ public class Playlist implements Parcelable {
dest.writeString(this.name); dest.writeString(this.name);
} }
@NonNull
public String getInfoString(@NonNull Context context) {
int songCount = getSongs(context).size();
String songCountString = MusicUtil.getSongCountString(context, songCount);
return MusicUtil.buildInfoString(
songCountString,
""
);
}
} }

View file

@ -0,0 +1,69 @@
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
/**
* @author SC (soncaokim)
*/
public class NotRecentlyPlayedPlaylist extends AbsSmartPlaylist {
public static final Creator<NotRecentlyPlayedPlaylist> CREATOR = new Creator<NotRecentlyPlayedPlaylist>() {
public NotRecentlyPlayedPlaylist createFromParcel(Parcel source) {
return new NotRecentlyPlayedPlaylist(source);
}
public NotRecentlyPlayedPlaylist[] newArray(int size) {
return new NotRecentlyPlayedPlaylist[size];
}
};
public NotRecentlyPlayedPlaylist(@NonNull Context context) {
super(context.getString(R.string.not_recently_played), R.drawable.ic_watch_later_white_24dp);
}
protected NotRecentlyPlayedPlaylist(Parcel in) {
super(in);
}
@NonNull
@Override
public String getInfoString(@NonNull Context context) {
String cutoff = PreferenceUtil.INSTANCE.getRecentlyPlayedCutoffText(context);
return MusicUtil.buildInfoString(
cutoff,
super.getInfoString(context)
);
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NonNull Context context) {
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getNotRecentlyPlayedTracks(context);
}
@Override
public void clear(@NonNull Context context) {
}
@Override
public boolean isClearable() {
return false;
}
@Override
public int describeContents() {
return 0;
}
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.mvp.presenter
import code.name.monkey.retromusic.model.Home
import code.name.monkey.retromusic.mvp.BaseView
import code.name.monkey.retromusic.mvp.Presenter
import code.name.monkey.retromusic.mvp.PresenterImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
interface HomeView : BaseView {
fun sections(sections: List<Home>)
}
interface HomePresenter : Presenter<HomeView> {
fun loadSections()
class HomePresenterImpl constructor(
private val repository: Repository
) : PresenterImpl<HomeView>(), HomePresenter, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
override fun detachView() {
super.detachView()
job.cancel()
}
override fun loadSections() {
launch {
val list = ArrayList<Home>()
val recentArtistResult = listOf(
repository.topArtists(),
repository.topAlbums(),
repository.recentArtists(),
repository.recentAlbums(),
repository.favoritePlaylist()
)
for (r in recentArtistResult) {
r?.let { list.add(it) }
}
withContext(Dispatchers.Main) {
if (list.isNotEmpty()) view?.sections(list) else view?.showEmptyView()
}
}
}
}
}

View file

@ -141,6 +141,21 @@ public class HistoryStore extends SQLiteOpenHelper {
RecentStoreColumns.TIME_PLAYED + " DESC"); RecentStoreColumns.TIME_PLAYED + " DESC");
} }
public Cursor queryRecentIds(long cutoff) {
final boolean noCutoffTime = (cutoff == 0);
final boolean reverseOrder = (cutoff < 0);
if (reverseOrder) cutoff = -cutoff;
final SQLiteDatabase database = getReadableDatabase();
return database.query(RecentStoreColumns.NAME,
new String[]{RecentStoreColumns.ID},
noCutoffTime ? null : RecentStoreColumns.TIME_PLAYED + (reverseOrder ? "<?" : ">?"),
noCutoffTime ? null : new String[]{String.valueOf(cutoff)},
null, null,
RecentStoreColumns.TIME_PLAYED + (reverseOrder ? " ASC" : " DESC"));
}
public interface RecentStoreColumns { public interface RecentStoreColumns {
String NAME = "recent_history"; String NAME = "recent_history";

View file

@ -19,6 +19,7 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.HomeAdapter import code.name.monkey.retromusic.adapter.HomeAdapter
import code.name.monkey.retromusic.loaders.* import code.name.monkey.retromusic.loaders.*
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.NotRecentlyPlayedPlaylist
import code.name.monkey.retromusic.providers.interfaces.Repository import code.name.monkey.retromusic.providers.interfaces.Repository
import code.name.monkey.retromusic.rest.LastFmClient import code.name.monkey.retromusic.rest.LastFmClient
import code.name.monkey.retromusic.rest.model.LastFmAlbum import code.name.monkey.retromusic.rest.model.LastFmAlbum
@ -41,6 +42,18 @@ class RepositoryImpl constructor(private val context: Context) : Repository {
override suspend fun artistById(artistId: Int): Artist = override suspend fun artistById(artistId: Int): Artist =
ArtistLoader.getArtist(context, artistId) ArtistLoader.getArtist(context, artistId)
override suspend fun suggestions(): Home? {
val songs = NotRecentlyPlayedPlaylist(context).getSongs(context).shuffled().subList(0, 9)
if (songs.isNotEmpty()) {
return Home(
songs,
HomeAdapter.SUGGESTIONS,
R.drawable.ic_audiotrack_white_24dp
)
}
return null
}
override suspend fun search(query: String?): MutableList<Any> = override suspend fun search(query: String?): MutableList<Any> =
SearchLoader.searchAll(context, query) SearchLoader.searchAll(context, query)
@ -58,8 +71,6 @@ class RepositoryImpl constructor(private val context: Context) : Repository {
override suspend fun recentArtists(): Home? { override suspend fun recentArtists(): Home? {
val artists = LastAddedSongsLoader.getLastAddedArtists(context) val artists = LastAddedSongsLoader.getLastAddedArtists(context)
return if (artists.isNotEmpty()) Home( return if (artists.isNotEmpty()) Home(
0,
R.string.recent_artists,
artists, artists,
HomeAdapter.RECENT_ARTISTS, HomeAdapter.RECENT_ARTISTS,
R.drawable.ic_artist_white_24dp R.drawable.ic_artist_white_24dp
@ -68,56 +79,40 @@ class RepositoryImpl constructor(private val context: Context) : Repository {
override suspend fun recentAlbums(): Home? { override suspend fun recentAlbums(): Home? {
val albums = LastAddedSongsLoader.getLastAddedAlbums(context) val albums = LastAddedSongsLoader.getLastAddedAlbums(context)
return if (albums.isNotEmpty()) { return if (albums.isNotEmpty()) Home(
Home( albums,
1, HomeAdapter.RECENT_ALBUMS,
R.string.recent_albums, R.drawable.ic_album_white_24dp
albums, ) else null
HomeAdapter.RECENT_ALBUMS,
R.drawable.ic_album_white_24dp
)
} else null
} }
override suspend fun topAlbums(): Home? { override suspend fun topAlbums(): Home? {
val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context) val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
return if (albums.isNotEmpty()) { return if (albums.isNotEmpty()) Home(
Home( albums,
3, HomeAdapter.TOP_ALBUMS,
R.string.top_albums, R.drawable.ic_album_white_24dp
albums, ) else null
HomeAdapter.TOP_ALBUMS,
R.drawable.ic_album_white_24dp
)
} else null
} }
override suspend fun topArtists(): Home? { override suspend fun topArtists(): Home? {
val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context) val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
return if (artists.isNotEmpty()) { return if (artists.isNotEmpty()) Home(
Home( artists,
2, HomeAdapter.TOP_ARTISTS,
R.string.top_artists, R.drawable.ic_artist_white_24dp
artists, ) else null
HomeAdapter.TOP_ARTISTS,
R.drawable.ic_artist_white_24dp
)
} else null
} }
override suspend fun favoritePlaylist(): Home? { override suspend fun favoritePlaylist(): Home? {
val playlists = PlaylistLoader.getFavoritePlaylist(context) val playlists = PlaylistLoader.getFavoritePlaylist(context)
return if (playlists.isNotEmpty()) { return if (playlists.isNotEmpty()) Home(
Home( playlists,
4, HomeAdapter.PLAYLISTS,
R.string.favorites, R.drawable.ic_favorite_white_24dp
playlists, ) else null
HomeAdapter.PLAYLISTS,
R.drawable.ic_favorite_white_24dp
)
} else null
} }
override suspend fun artistInfo( override suspend fun artistInfo(

View file

@ -42,6 +42,11 @@ interface Repository {
suspend fun getGenre(genreId: Int): ArrayList<Song> suspend fun getGenre(genreId: Int): ArrayList<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): Home? suspend fun recentArtists(): Home?
suspend fun topArtists(): Home? suspend fun topArtists(): Home?
@ -52,9 +57,5 @@ interface Repository {
suspend fun favoritePlaylist(): Home? suspend fun favoritePlaylist(): Home?
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist suspend fun suggestions(): Home?
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
} }

View file

@ -127,4 +127,16 @@ public class CalendarUtil {
final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1); final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1);
return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH); return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH);
} }
/**
* Returns the time elapsed so far last N days in milliseconds.
*
* @return Time elapsed since N days in milliseconds.
*/
public long getElapsedDays(int numDays) {
long elapsed = getElapsedToday();
elapsed += numDays * MS_PER_DAY;
return elapsed;
}
} }

View file

@ -1,5 +1,6 @@
package code.name.monkey.retromusic.util package code.name.monkey.retromusic.util
import android.content.Context
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkInfo import android.net.NetworkInfo
@ -23,6 +24,7 @@ import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import java.io.File import java.io.File
object PreferenceUtil { object PreferenceUtil {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext()) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext())
@ -537,6 +539,44 @@ object PreferenceUtil {
) )
} }
fun getRecentlyPlayedCutoffTimeMillis(): Long {
return getCutoffTimeMillis(RECENTLY_PLAYED_CUTOFF)
}
fun getRecentlyPlayedCutoffText(context: Context): String? {
return getCutoffText(RECENTLY_PLAYED_CUTOFF, context)
}
private fun getCutoffText(
cutoff: String,
context: Context
): String? {
return when (sharedPreferences.getString(cutoff, "")) {
"today" -> context.getString(R.string.today)
"this_week" -> context.getString(R.string.this_week)
"past_seven_days" -> context.getString(R.string.past_seven_days)
"past_three_months" -> context.getString(R.string.past_three_months)
"this_year" -> context.getString(R.string.this_year)
"this_month" -> context.getString(R.string.this_month)
else -> context.getString(R.string.this_month)
}
}
private fun getCutoffTimeMillis(cutoff: String): Long {
val calendarUtil = CalendarUtil()
val interval: Long
interval = when (sharedPreferences.getString(cutoff, "")) {
"today" -> calendarUtil.elapsedToday
"this_week" -> calendarUtil.elapsedWeek
"past_seven_days" -> calendarUtil.getElapsedDays(7)
"past_three_months" -> calendarUtil.getElapsedMonths(3)
"this_year" -> calendarUtil.elapsedYear
"this_month" -> calendarUtil.elapsedMonth
else -> calendarUtil.elapsedMonth
}
return System.currentTimeMillis() - interval
}
val lastAddedCutoff: Long val lastAddedCutoff: Long
get() { get() {
val calendarUtil = CalendarUtil() val calendarUtil = CalendarUtil()

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/md_white_1000"
android:pathData="M4.27 3L3 4.27L12 13.27V13.55C11.41 13.21 10.73 13 10 13C7.79 13 6 14.79 6 17S7.79 21 10 21 14 19.21 14 17V15.27L19.73 21L21 19.73L4.27 3M14 7H18V3H12V8.18L14 10.18Z" />
</vector>

View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:padding="4dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:text="@string/suggestion_songs"
android:textAppearance="@style/TextViewHeadline6"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card1"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card2"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card2"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card3"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card1"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card3"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card4"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card2"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card4"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card5"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card3"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image7"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card5"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card4"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image8"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card6"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="?attr/colorSurface"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card7"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:text="New music mix"
android:textAppearance="@style/TextViewHeadline4"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card7"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card8"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card6"
app:layout_constraintTop_toBottomOf="@id/title">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card8"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toEndOf="@id/card7"
app:layout_constraintTop_toBottomOf="@id/title">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card9"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card8"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card6"
app:layout_constraintTop_toBottomOf="@id/card7">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -857,6 +857,8 @@
<string name="help_summary">Need more help?</string> <string name="help_summary">Need more help?</string>
<string name="gradient">Gradient</string> <string name="gradient">Gradient</string>
<string name="user_name">User Name</string> <string name="user_name">User Name</string>
<string name="not_recently_played">Not recently played</string>
<string name="past_seven_days">Past 7 days</string>
<plurals name="albumSongs"> <plurals name="albumSongs">
<item quantity="one">Song</item> <item quantity="one">Song</item>

View file

@ -28,7 +28,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha05' implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
// Used for the list preference classes // Used for the list preference classes
def material_dialog_version = "0.9.6.0" def material_dialog_version = "0.9.6.0"