diff --git a/.gitignore b/.gitignore index 37f05b7f..6a32941f 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,4 @@ obj/ captures app/normal/release/ /models/ - -app/font/ -app/src/debug/ -/app/nofont/ -/crowdin.properties +/app/release/ diff --git a/README.md b/README.md index 147dc995..66b64c0d 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ favorite songs. No other music player has this feature. We are trying our best to bring you the best user experience. The app is regulary being updated for bug fixes and new features. -### FAQ +### ❓ FAQ Please read the FAQ here: https://del.dog/RetroFaq In any case, you find or notice any bugs please report them by diff --git a/app/build.gradle b/app/build.gradle index 3f4414d0..ebb6025a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,7 +34,7 @@ android { } signingConfigs { release { - Properties properties = getProperties('/Users/h4h13/Documents/Github/retro.properties') + Properties properties = getProperties('/Users/apple/Documents/Github/retro.properties ') storeFile file(getProperty(properties, 'storeFile')) keyAlias getProperty(properties, 'keyAlias') storePassword getProperty(properties, 'storePassword') @@ -102,68 +102,50 @@ static def getDate() { dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':appthemehelper') implementation 'androidx.multidex:multidex:2.0.1' - implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.cardview:cardview:1.0.0" - implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01" implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.palette:palette-ktx:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + def nav_version = "2.3.0" + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - implementation 'com.google.android.material:material:1.3.0-alpha01' - - def retrofit_version = '2.9.0' - implementation "com.squareup.retrofit2:retrofit:$retrofit_version" - implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" - - def material_dialog_version = "0.9.6.0" - implementation "com.afollestad.material-dialogs:core:$material_dialog_version" - implementation "com.afollestad.material-dialogs:commons:$material_dialog_version" - implementation 'com.afollestad:material-cab:0.1.12' - - implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' - implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' - - implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') { - transitive = true - } - - def kotlin_coroutines_version = "1.3.8" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - - implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' - - implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' - implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod' - - implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3' - - implementation 'com.anjlab.android.iab.v3:library:1.1.0' - implementation 'com.r0adkll:slidableactivity:2.1.0' - implementation 'com.heinrichreimersoftware:material-intro:1.6' - implementation 'me.zhanghai.android.fastscroll:library:1.1.0' + def room_version = "2.2.5" + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-ktx:$room_version" + kapt "androidx.room:room-compiler:$room_version" def lifecycle_version = "2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" - implementation 'com.google.android.play:core:1.8.0' - implementation 'me.jorgecastillo:androidcolorx:0.2.0' - debugImplementation 'com.amitshekhar.android:debug-db:1.0.4' - implementation 'com.github.dhaval2404:imagepicker:1.7.1' + implementation 'com.google.android.play:core-ktx:1.8.1' + implementation 'com.google.android.material:material:1.3.0-alpha01' + + def retrofit_version = '2.9.0' + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' + + def material_dialog_version = "0.9.6.0" + implementation "com.afollestad.material-dialogs:core:$material_dialog_version" + implementation "com.afollestad.material-dialogs:commons:$material_dialog_version" + implementation 'com.afollestad:material-cab:0.1.12' + + def kotlin_coroutines_version = "1.3.8" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" def koin_version = "2.1.5" implementation "org.koin:koin-core:$koin_version" @@ -173,8 +155,22 @@ dependencies { implementation "org.koin:koin-androidx-fragment:$koin_version" implementation "org.koin:koin-androidx-ext:$koin_version" - def nav_version = "2.3.0" - implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" - implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + implementation 'com.github.bumptech.glide:glide:3.8.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' + implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' + + implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' + implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' + implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod' + implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3' + implementation 'com.anjlab.android.iab.v3:library:1.1.0' + implementation 'com.r0adkll:slidableactivity:2.1.0' + implementation 'com.heinrichreimersoftware:material-intro:1.6' + implementation 'com.github.dhaval2404:imagepicker:1.7.1' + implementation 'org.jsoup:jsoup:1.11.1' + implementation 'me.zhanghai.android.fastscroll:library:1.1.0' + implementation 'me.jorgecastillo:androidcolorx:0.2.0' + implementation 'org.jsoup:jsoup:1.11.1' + debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' } \ No newline at end of file diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json deleted file mode 100644 index b45c7e16..00000000 --- a/app/release/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "code.name.monkey.retromusic", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "properties": [], - "versionCode": 10438, - "versionName": "3.5.650_0812", - "enabled": true, - "outputFile": "app-release.apk" - } - ] -} \ No newline at end of file diff --git a/app/src/debug/res/font/bold.ttf b/app/src/debug/res/font/bold.ttf new file mode 100644 index 00000000..96619df9 Binary files /dev/null and b/app/src/debug/res/font/bold.ttf differ diff --git a/app/src/debug/res/font/google_sans_bold.ttf b/app/src/debug/res/font/google_sans_bold.ttf new file mode 100644 index 00000000..80497666 Binary files /dev/null and b/app/src/debug/res/font/google_sans_bold.ttf differ diff --git a/app/src/debug/res/font/google_sans_medium.ttf b/app/src/debug/res/font/google_sans_medium.ttf new file mode 100644 index 00000000..1543660d Binary files /dev/null and b/app/src/debug/res/font/google_sans_medium.ttf differ diff --git a/app/src/debug/res/font/google_sans_regular.ttf b/app/src/debug/res/font/google_sans_regular.ttf new file mode 100644 index 00000000..ab605f9e Binary files /dev/null and b/app/src/debug/res/font/google_sans_regular.ttf differ diff --git a/app/src/debug/res/font/medium.ttf b/app/src/debug/res/font/medium.ttf new file mode 100644 index 00000000..fd818d6f Binary files /dev/null and b/app/src/debug/res/font/medium.ttf differ diff --git a/app/src/debug/res/font/regular.ttf b/app/src/debug/res/font/regular.ttf new file mode 100644 index 00000000..e2c69c3f Binary files /dev/null and b/app/src/debug/res/font/regular.ttf differ diff --git a/app/src/debug/res/font/sans.xml b/app/src/debug/res/font/sans.xml new file mode 100644 index 00000000..7bbc8513 --- /dev/null +++ b/app/src/debug/res/font/sans.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/debug/res/values/styles.xml b/app/src/debug/res/values/styles.xml new file mode 100644 index 00000000..81154a39 --- /dev/null +++ b/app/src/debug/res/values/styles.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bb4f033d..11590b31 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -255,11 +255,9 @@ + - diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index f9c45db6..a4a872d3 100644 Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt index 0f037ba7..4e3a7a24 100644 --- a/app/src/main/java/code/name/monkey/retromusic/Constants.kt +++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt @@ -31,7 +31,7 @@ object Constants { const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp" const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md" const val PINTEREST = "https://in.pinterest.com/retromusicapp/" - const val BASE_URL = "https://ws.audioscrobbler.com/2.0/" + const val AUDIO_SCROBBLER_URL = "https://ws.audioscrobbler.com/2.0/" const val IS_MUSIC = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''" @@ -55,9 +55,11 @@ object Constants { } const val EXTRA_GENRE = "extra_genre" const val EXTRA_PLAYLIST = "extra_playlist" +const val EXTRA_PLAYLIST_ID = "extra_playlist_id" const val EXTRA_ALBUM_ID = "extra_album_id" const val EXTRA_ARTIST_ID = "extra_artist_id" const val EXTRA_SONG = "extra_songs" +const val EXTRA_PLAYLISTS = "extra_playlists" const val LIBRARY_CATEGORIES = "library_categories" const val EXTRA_SONG_INFO = "extra_song_info" const val DESATURATED_COLOR = "desaturated_color" @@ -68,8 +70,8 @@ const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id" const val CAROUSEL_EFFECT = "carousel_effect" const val COLORED_NOTIFICATION = "colored_notification" const val CLASSIC_NOTIFICATION = "classic_notification" -const val GAPLESS_PLAYBACK = "gapless_playback" -const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen" +const val GAP_LESS_PLAYBACK = "gap_less_playback" +const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen" const val BLURRED_ALBUM_ART = "blurred_album_art" const val NEW_BLUR_AMOUNT = "new_blur_amount" const val TOGGLE_HEADSET = "toggle_headset" @@ -90,7 +92,6 @@ const val ALBUM_COVER_STYLE = "album_cover_style_id" const val ALBUM_COVER_TRANSFORM = "album_cover_transform" const val TAB_TEXT_MODE = "tab_text_mode" const val LANGUAGE_NAME = "language_name" -const val DIALOG_CORNER = "dialog_corner" const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song" const val ALBUM_GRID_STYLE = "album_grid_style_home" const val ARTIST_GRID_STYLE = "artist_grid_style_home" diff --git a/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt index 80574b8a..f5312054 100644 --- a/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt +++ b/app/src/main/java/code/name/monkey/retromusic/HomeSection.kt @@ -22,4 +22,7 @@ const val TOP_ARTISTS = 0 const val SUGGESTIONS = 5 const val FAVOURITES = 4 const val GENRES = 6 -const val PLAYLISTS = 7 \ No newline at end of file +const val PLAYLISTS = 7 +const val HISTORY_PLAYLIST = 8 +const val LAST_ADDED_PLAYLIST = 9 +const val TOP_PLAYED_PLAYLIST = 10 \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/MainModule.kt b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt index f1e52df6..c7296dda 100644 --- a/app/src/main/java/code/name/monkey/retromusic/MainModule.kt +++ b/app/src/main/java/code/name/monkey/retromusic/MainModule.kt @@ -1,5 +1,12 @@ package code.name.monkey.retromusic +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.db.SupportSQLiteDatabase +import code.name.monkey.retromusic.db.BlackListStoreDao +import code.name.monkey.retromusic.db.BlackListStoreEntity +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.RetroDatabase import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel @@ -7,17 +14,103 @@ import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel import code.name.monkey.retromusic.fragments.search.SearchViewModel import code.name.monkey.retromusic.model.Genre -import code.name.monkey.retromusic.model.Playlist -import code.name.monkey.retromusic.network.networkModule +import code.name.monkey.retromusic.network.* import code.name.monkey.retromusic.repository.* +import code.name.monkey.retromusic.util.FilePathUtil +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.bind import org.koin.dsl.module +val networkModule = module { + + factory { + provideDefaultCache() + } + factory { + provideOkHttp(get(), get()) + } + single { + provideLastFmRetrofit(get()) + } + single { + provideDeezerRest(get()) + } + single { + provideLastFmRest(get()) + } + single { + provideLyrics(get()) + } +} + +private val roomModule = module { + + single { + Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db") + .allowMainThreadQueries() + .addCallback(object : RoomDatabase.Callback() { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + GlobalScope.launch(IO) { + FilePathUtil.blacklistFilePaths().map { + get().insertBlacklistPath(BlackListStoreEntity(it)) + } + } + } + }) + .fallbackToDestructiveMigration() + .build() + } + factory { + get().lyricsDao() + } + + factory { + get().playlistDao() + } + + factory { + get().blackListStore() + } + + factory { + get().playCountDao() + } + + factory { + get().historyDao() + } + + single { + RealRoomRepository(get(), get(), get(), get(), get()) + } bind RoomRepository::class +} +private val mainModule = module { + single { + androidContext().contentResolver + } + +} private val dataModule = module { single { - RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) + RealRepository( + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get(), + get() + ) } bind Repository::class single { @@ -61,10 +154,6 @@ private val dataModule = module { get() ) } - - single { - androidContext().contentResolver - } } private val viewModules = module { @@ -87,7 +176,7 @@ private val viewModules = module { ) } - viewModel { (playlist: Playlist) -> + viewModel { (playlist: PlaylistWithSongs) -> PlaylistDetailsViewModel( get(), playlist @@ -106,4 +195,4 @@ private val viewModules = module { } } -val appModules = listOf(dataModule, viewModules, networkModule) \ No newline at end of file +val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt index 4d6cef1f..13b75956 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/DriveModeActivity.kt @@ -218,7 +218,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback { .build() .transform(BlurTransformation.Builder(this).build()) .into(object : RetroMusicColoredTarget(image) { - override fun onColorReady(color: MediaNotificationProcessor) { + override fun onColorReady(colors: MediaNotificationProcessor) { } }) } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt index 16491acc..ff855947 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt @@ -3,30 +3,23 @@ package code.name.monkey.retromusic.activities import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.net.Uri import android.os.Bundle import android.provider.MediaStore -import android.util.Log import android.view.View import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.* import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.extensions.findNavController -import code.name.monkey.retromusic.fragments.LibraryViewModel -import code.name.monkey.retromusic.helper.MusicPlayerRemote.openAndShuffleQueue -import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue -import code.name.monkey.retromusic.helper.MusicPlayerRemote.playFromUri -import code.name.monkey.retromusic.helper.MusicPlayerRemote.shuffleMode +import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.repository.PlaylistSongsLoader.getPlaylistSongList -import code.name.monkey.retromusic.repository.Repository +import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.AppRater.appLaunched import code.name.monkey.retromusic.util.PreferenceUtil -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject -import java.util.* class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { companion object { @@ -35,9 +28,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis const val APP_UPDATE_REQUEST_CODE = 9002 } - private val repository by inject() - private val libraryViewModel by inject() - private var blockRequestPermissions = false override fun createContentView(): View { @@ -53,7 +43,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis setTaskDescriptionColorAuto() hideStatusBar() appLaunched(this) - addMusicServiceEventListener(libraryViewModel) updateTabs() } @@ -99,61 +88,68 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis override fun onServiceConnected() { super.onServiceConnected() - handlePlaybackIntent(intent) - } - - private fun handlePlaybackIntent(intent: Intent?) { if (intent == null) { return } - val uri = intent.data - val mimeType = intent.type - var handled = false - if (intent.action != null && (intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) - ) { - val songs: List = - getSongs(this, intent.extras!!) - if (shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) { - openAndShuffleQueue(songs, true) - } else { - openQueue(songs, 0, true) - } - handled = true - } - if (uri != null && uri.toString().isNotEmpty()) { - playFromUri(uri) - handled = true - } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { - val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt() - if (id >= 0) { - val position = intent.getIntExtra("position", 0) - val songs: List = - ArrayList(getPlaylistSongList(this, id)) - openQueue(songs, position, true) + handlePlaybackIntent(intent) + } + + private fun handlePlaybackIntent(intent: Intent) { + lifecycleScope.launch(IO) { + val uri: Uri? = intent.data + val mimeType: String? = intent.type + var handled = false + if (intent.action != null && + intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH + ) { + val songs: List = getSongs(intent.extras!!) + if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) { + MusicPlayerRemote.openAndShuffleQueue(songs, true) + } else { + MusicPlayerRemote.openQueue(songs, 0, true) + } handled = true } - } else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) { - val id = parseIdFromIntent(intent, "albumId", "album").toInt() - if (id >= 0) { - lifecycleScope.launch(Dispatchers.Main) { - val position = intent.getIntExtra("position", 0) - openQueue(repository.albumById(id).songs!!, position, true) + if (uri != null && uri.toString().isNotEmpty()) { + MusicPlayerRemote.playFromUri(uri) + handled = true + } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { + val id: Int = parseIdFromIntent(intent, "playlistId", "playlist").toInt() + if (id >= 0) { + val position: Int = intent.getIntExtra("position", 0) + val songs: List = + PlaylistSongsLoader.getPlaylistSongList(this@MainActivity, id) + MusicPlayerRemote.openQueue(songs, position, true) + handled = true + } + } else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) { + val id = parseIdFromIntent(intent, "albumId", "album").toInt() + if (id >= 0) { + val position: Int = intent.getIntExtra("position", 0) + MusicPlayerRemote.openQueue( + libraryViewModel.albumById(id).songs!!, + position, + true + ) + handled = true + } + } else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) { + val id: Int = parseIdFromIntent(intent, "artistId", "artist").toInt() + if (id >= 0) { + val position: Int = intent.getIntExtra("position", 0) + MusicPlayerRemote.openQueue( + libraryViewModel.artistById(id).songs, + position, + true + ) handled = true } } - } else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) { - val id = parseIdFromIntent(intent, "artistId", "artist").toInt() - if (id >= 0) { - lifecycleScope.launch { - val position = intent.getIntExtra("position", 0) - openQueue(repository.artistById(id).songs, position, true) - handled = true - } + if (handled) { + setIntent(Intent()) } } - if (handled) { - setIntent(Intent()) - } + } private fun parseIdFromIntent( @@ -167,7 +163,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis try { id = idString.toLong() } catch (e: NumberFormatException) { - Log.e(TAG, e.message) + println(e.message) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt index e15d4e15..9906db0b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PlayingQueueActivity.kt @@ -104,7 +104,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() { } } }) - val fastScroller = ThemedFastScroller.create(recyclerView) + ThemedFastScroller.create(recyclerView) } private fun checkForPadding() { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt index 410d6814..99d30ab7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt @@ -108,7 +108,7 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler { super.onDestroy() } - private class RestorePurchaseAsyncTask internal constructor(purchaseActivity: PurchaseActivity) : + private class RestorePurchaseAsyncTask(purchaseActivity: PurchaseActivity) : AsyncTask() { private val buyActivityWeakReference: WeakReference = WeakReference( diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt index 66bd0aa8..b20d7045 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt @@ -110,7 +110,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH } if (requestCode == TEZ_REQUEST_CODE) { // Process based on the data in response. - Log.d("result", data!!.getStringExtra("Status")) + //Log.d("result", data!!.getStringExtra("Status")) } } @@ -121,7 +121,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH } } -private class SkuDetailsLoadAsyncTask internal constructor(supportDevelopmentActivity: SupportDevelopmentActivity) : +private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelopmentActivity) : AsyncTask>() { private val weakReference: WeakReference = WeakReference( diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt index 904f48ff..a97642f4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/UserInfoActivity.kt @@ -160,7 +160,7 @@ class UserInfoActivity : AbsBaseActivity() { } private fun saveImage(bitmap: Bitmap, fileName: String) { - CoroutineScope(Dispatchers.IO).launch() { + CoroutineScope(Dispatchers.IO).launch { val appDir = applicationContext.filesDir val file = File(appDir, fileName) var successful = false diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt index 9057625f..1e9efee8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt @@ -4,17 +4,23 @@ import android.Manifest import android.content.* import android.os.Bundle import android.os.IBinder +import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.toPlayCount import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.MusicServiceEventListener +import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.service.MusicService.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject import java.lang.ref.WeakReference import java.util.* abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener { private val mMusicServiceEventListeners = ArrayList() - + private val repository: RealRepository by inject() private var serviceToken: MusicPlayerRemote.ServiceToken? = null private var musicStateReceiver: MusicStateReceiver? = null private var receiverRegistered: Boolean = false @@ -93,6 +99,22 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis for (listener in mMusicServiceEventListeners) { listener.onPlayingMetaChanged() } + lifecycleScope.launch(Dispatchers.IO) { + val entity = repository.songPresentInHistory(MusicPlayerRemote.currentSong) + if (entity != null) { + repository.updateHistorySong(MusicPlayerRemote.currentSong) + } else { + repository.addSongToHistory(MusicPlayerRemote.currentSong) + } + val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id) + if (songs.isNotEmpty()) { + repository.updateSongInPlayCount(songs.first().apply { + playCount += 1 + }) + } else { + repository.insertSongInPlayCount(MusicPlayerRemote.currentSong.toPlayCount()) + } + } } override fun onQueueChanged() { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index d0dbdb34..54098279 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -15,7 +15,6 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.extensions.hide -import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.MiniPlayerFragment @@ -29,12 +28,12 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import org.koin.androidx.viewmodel.ext.android.viewModel -abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() { +abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { companion object { val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName } - private val libraryViewModel by viewModel() + protected val libraryViewModel by viewModel() private lateinit var behavior: RetroBottomSheetBehavior private var miniPlayerFragment: MiniPlayerFragment? = null private var cps: NowPlayingScreen? = null @@ -51,8 +50,6 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() { override fun onSlide(bottomSheet: View, slideOffset: Float) { setMiniPlayerAlphaProgress(slideOffset) - dimBackground.show() - dimBackground.alpha = slideOffset } override fun onStateChanged(bottomSheet: View, newState: Int) { @@ -62,7 +59,6 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() { } BottomSheetBehavior.STATE_COLLAPSED -> { onPanelCollapsed() - dimBackground.hide() } else -> { @@ -77,13 +73,9 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() { setContentView(createContentView()) chooseFragmentForTheme() setupSlidingUpPanel() - addMusicServiceEventListener(libraryViewModel) setupBottomSheet() - val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY) - dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) - libraryViewModel.paletteColorLiveData.observe(this, Observer { this.paletteColor = it onPaletteColorChanged() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt index 418ded1d..5bb2b7ac 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/BugReportActivity.kt @@ -295,7 +295,7 @@ open class BugReportActivity : AbsThemeActivity() { .setTitle(R.string.bug_report_failed) .setMessage(R.string.bug_report_failed_unknown) .setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() } - .setNegativeButton(android.R.string.cancel) { _, _ -> { tryToFinishActivity() } } + .setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java index e9eee8a2..3a6e8032 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java +++ b/app/src/main/java/code/name/monkey/retromusic/activities/bugreport/model/DeviceInfo.java @@ -85,7 +85,7 @@ public class DeviceInfo { return "Device info:\n" + "---\n" + "\n" - + "\n" + + "\n" + "\n" + "\n" + "\n" diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt index d9f006e7..1571e07a 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AbsTagEditorActivity.kt @@ -14,7 +14,6 @@ import android.view.MenuItem import android.view.View import android.view.animation.OvershootInterpolator import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.lifecycleScope import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil @@ -182,11 +181,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { saveFab = findViewById(R.id.saveTags) getIntentExtras() - lifecycleScope.launchWhenCreated { - songPaths = getSongPaths() - if (songPaths!!.isEmpty()) { - finish() - } + songPaths = getSongPaths() + if (songPaths!!.isEmpty()) { + finish() } setUpViews() } @@ -258,7 +255,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } - protected abstract suspend fun getSongPaths(): List + protected abstract fun getSongPaths(): List protected fun searchWebFor(vararg keys: String) { val stringBuilder = StringBuilder() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt index ca67e93b..8a908dd6 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt @@ -44,9 +44,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { window.enterTransition = slide } - override fun loadImageFromFile(selectedFileUri: Uri?) { + override fun loadImageFromFile(selectedFile: Uri?) { - Glide.with(this@AlbumTagEditorActivity).load(selectedFileUri).asBitmap() + Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap() .transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java) .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) .into(object : SimpleTarget() { @@ -167,7 +167,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { ) } - override suspend fun getSongPaths(): List { + override fun getSongPaths(): List { val songs = repository.albumById(id).songs val paths = ArrayList(songs!!.size) for (song in songs) { diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt index 5e3d5909..35b02bda 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt @@ -88,7 +88,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { writeValuesToFiles(fieldKeyValueMap, null) } - override suspend fun getSongPaths(): List { + override fun getSongPaths(): List { val paths = ArrayList(1) paths.add(songRepository.song(id).data) return paths diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt index a015b1ad..bf9f1c91 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/GenreAdapter.kt @@ -1,5 +1,6 @@ package code.name.monkey.retromusic.adapter +import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -22,14 +23,14 @@ class GenreAdapter( var dataSet: List, private val mItemLayoutRes: Int ) : RecyclerView.Adapter() { - - + val colors = listOf(Color.RED, Color.BLUE) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val genre = dataSet[position] + holder.title?.text = genre.name holder.text?.text = String.format( Locale.getDefault(), diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt index b08b15df..0ed2272b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/HomeAdapter.kt @@ -40,8 +40,8 @@ class HomeAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val layout = LayoutInflater.from(activity) - .inflate(R.layout.section_recycler_view, parent, false) + val layout = + LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false) return when (viewType) { RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) GENRES -> GenreViewHolder(layout) @@ -64,7 +64,7 @@ class HomeAdapter( when (getItemViewType(position)) { RECENT_ALBUMS -> { val viewHolder = holder as AlbumViewHolder - viewHolder.bindView(home.arrayList as List, R.string.recent_albums) + viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, @@ -74,7 +74,7 @@ class HomeAdapter( } TOP_ALBUMS -> { val viewHolder = holder as AlbumViewHolder - viewHolder.bindView(home.arrayList as List, R.string.top_albums) + viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, @@ -84,7 +84,7 @@ class HomeAdapter( } RECENT_ARTISTS -> { val viewHolder = holder as ArtistViewHolder - viewHolder.bindView(home.arrayList, R.string.recent_artists) + viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, @@ -94,7 +94,7 @@ class HomeAdapter( } TOP_ARTISTS -> { val viewHolder = holder as ArtistViewHolder - viewHolder.bindView(home.arrayList, R.string.top_artists) + viewHolder.bindView(home) viewHolder.clickableArea.setOnClickListener { activity.findNavController(R.id.fragment_container).navigate( R.id.detailListFragment, @@ -104,15 +104,21 @@ class HomeAdapter( } SUGGESTIONS -> { val viewHolder = holder as SuggestionsViewHolder - viewHolder.bindView(home.arrayList) + viewHolder.bindView(home) } FAVOURITES -> { val viewHolder = holder as PlaylistViewHolder - viewHolder.bindView(home.arrayList, R.string.favorites) + viewHolder.bindView(home) + viewHolder.clickableArea.setOnClickListener { + activity.findNavController(R.id.fragment_container).navigate( + R.id.detailListFragment, + bundleOf("type" to FAVOURITES) + ) + } } GENRES -> { val viewHolder = holder as GenreViewHolder - viewHolder.bind(home.arrayList, R.string.genres) + viewHolder.bind(home) } PLAYLISTS -> { @@ -130,22 +136,22 @@ class HomeAdapter( } private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) { - fun bindView(albums: List, titleRes: Int) { - title.text = activity.getString(titleRes) + fun bindView(home: Home) { + title.setText(home.titleRes) recyclerView.apply { - adapter = albumAdapter(albums) + adapter = albumAdapter(home.arrayList as List) layoutManager = gridLayoutManager() } } } private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) { - fun bindView(artists: List, titleRes: Int) { + fun bindView(home: Home) { + title.setText(home.titleRes) recyclerView.apply { layoutManager = linearLayoutManager() - adapter = artistsAdapter(artists as List) + adapter = artistsAdapter(home.arrayList as List) } - title.text = activity.getString(titleRes) } } @@ -161,8 +167,7 @@ class HomeAdapter( R.id.image8 ) - fun bindView(songs: List) { - songs as List + fun bindView(home: Home) { val color = ThemeStore.accentColor(activity) itemView.findViewById(R.id.message).setTextColor(color) itemView.findViewById(R.id.card6).apply { @@ -170,9 +175,9 @@ class HomeAdapter( } images.forEachIndexed { index, id -> itemView.findViewById(id).setOnClickListener { - MusicPlayerRemote.playNext(songs[index]) + MusicPlayerRemote.playNext(home.arrayList[index] as Song) } - SongGlideRequest.Builder.from(Glide.with(activity), songs[index]) + SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song) .asBitmap() .build() .into(itemView.findViewById(id)) @@ -182,35 +187,37 @@ class HomeAdapter( } private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) { - fun bindView(songs: List, titleRes: Int) { - arrow.hide() + fun bindView(home: Home) { + title.setText(home.titleRes) recyclerView.apply { val songAdapter = SongAdapter( activity, - songs as MutableList, + home.arrayList as MutableList, R.layout.item_album_card, null ) layoutManager = linearLayoutManager() adapter = songAdapter } - title.text = activity.getString(titleRes) } } private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) { - fun bind(genres: List, titleRes: Int) { + fun bind(home: Home) { arrow.hide() - title.text = activity.getString(titleRes) + title.setText(home.titleRes) + val genreAdapter = GenreAdapter( + activity, + home.arrayList as List, + R.layout.item_grid_genre + ) recyclerView.apply { layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false) - val genreAdapter = - GenreAdapter(activity, genres as List, R.layout.item_grid_genre) adapter = genreAdapter } } } - open inner class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) { + open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) { val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView) val title: AppCompatTextView = itemView.findViewById(R.id.title) val arrow: ImageView = itemView.findViewById(R.id.arrow) diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt index c372b45c..97e2ada1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/SearchAdapter.kt @@ -60,7 +60,7 @@ class SearchAdapter( holder.title?.text = album.title holder.text?.text = album.artistName AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore(activity).build().into(holder.image) + .checkIgnoreMediaStore().build().into(holder.image) } ARTIST -> { val artist = dataSet.get(position) as Artist diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt index 73c8b09e..8cb9f9d8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumAdapter.kt @@ -101,11 +101,10 @@ open class AlbumAdapter( } AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore(activity) + .checkIgnoreMediaStore() .generatePalette(activity) .build() .into(object : RetroMusicColoredTarget(holder.image!!) { - override fun onColorReady(colors: MediaNotificationProcessor) { setColors(colors, holder) } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt index 039aa9fe..1a82f465 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.dialogs.LyricsDialog import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.glide.RetroMusicColoredTarget @@ -90,6 +91,7 @@ class AlbumCoverPagerAdapter( val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) albumCover = view.findViewById(R.id.player_image) albumCover.setOnClickListener { + LyricsDialog().show(childFragmentManager, "LyricsDialog") showLyricsDialog() } return view @@ -97,7 +99,7 @@ class AlbumCoverPagerAdapter( private fun showLyricsDialog() { lifecycleScope.launch(Dispatchers.IO) { - val data = MusicUtil.getLyrics(song) + val data: String = MusicUtil.getLyrics(song) ?: "No lyrics found" withContext(Dispatchers.Main) { MaterialAlertDialogBuilder( requireContext(), diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt index 9b8dcb03..8198a957 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/HorizontalAlbumAdapter.kt @@ -3,7 +3,6 @@ package code.name.monkey.retromusic.adapter.album import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentActivity -import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.retromusic.fragments.albums.AlbumClickListener import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.RetroMusicColoredTarget @@ -30,14 +29,14 @@ class HorizontalAlbumAdapter( } override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { - holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) - holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) + //holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) + //holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) } override fun loadAlbumCover(album: Album, holder: ViewHolder) { if (holder.image == null) return AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) - .checkIgnoreMediaStore(activity) + .checkIgnoreMediaStore() .generatePalette(activity) .build() .into(object : RetroMusicColoredTarget(holder.image!!) { diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt new file mode 100644 index 00000000..c74c8a5f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/LegacyPlaylistAdapter.kt @@ -0,0 +1,52 @@ +package code.name.monkey.retromusic.adapter.playlist + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.RecyclerView +import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder +import code.name.monkey.retromusic.model.Playlist +import code.name.monkey.retromusic.util.MusicUtil + +class LegacyPlaylistAdapter( + private val activity: FragmentActivity, + private var list: List, + private val layoutRes: Int, + private val playlistClickListener: PlaylistClickListener +) : + RecyclerView.Adapter() { + + fun swapData(list: List) { + this.list = list + notifyDataSetChanged() + } + + class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ViewHolder { + return ViewHolder( + LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val playlist: Playlist = list[position] + holder.title?.text = playlist.name + holder.text?.text = MusicUtil.getPlaylistInfoString(activity, playlist.getSongs()) + holder.itemView.setOnClickListener { + playlistClickListener.onPlaylistClick(playlist) + } + } + + override fun getItemCount(): Int { + return list.size + } + + interface PlaylistClickListener { + fun onPlaylistClick(playlist: Playlist) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt index 39d4f8e1..1d21b803 100755 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt @@ -13,51 +13,50 @@ import androidx.appcompat.widget.PopupMenu import androidx.core.os.bundleOf import androidx.fragment.app.FragmentActivity import androidx.navigation.findNavController -import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.SongEntity +import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.interfaces.CabHolder -import code.name.monkey.retromusic.model.AbsCustomPlaylist import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.RetroColorUtil -import java.util.* class PlaylistAdapter( private val activity: FragmentActivity, - var dataSet: List, + var dataSet: List, private var itemLayoutRes: Int, cabHolder: CabHolder? -) : AbsMultiSelectAdapter( +) : AbsMultiSelectAdapter( activity, cabHolder, R.menu.menu_playlists_selection ) { - init { setHasStableIds(true) } - fun swapDataSet(dataSet: List) { + fun swapDataSet(dataSet: List) { this.dataSet = dataSet notifyDataSetChanged() } override fun getItemId(position: Int): Long { - return dataSet[position].id.toLong() + return dataSet[position].playlistEntity.playListId.toLong() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -69,20 +68,20 @@ class PlaylistAdapter( return ViewHolder(view) } - private fun getPlaylistTitle(playlist: Playlist): String { - return if (TextUtils.isEmpty(playlist.name)) "-" else playlist.name + private fun getPlaylistTitle(playlist: PlaylistEntity): String { + return if (TextUtils.isEmpty(playlist.playlistName)) "-" else playlist.playlistName } - private fun getPlaylistText(playlist: Playlist): String { - return MusicUtil.getPlaylistInfoString(activity, getSongs(playlist)) + private fun getPlaylistText(playlist: PlaylistWithSongs): String { + return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs()) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val playlist = dataSet[position] holder.itemView.isActivated = isChecked(playlist) - holder.title?.text = getPlaylistTitle(playlist) + holder.title?.text = getPlaylistTitle(playlist.playlistEntity) holder.text?.text = getPlaylistText(playlist) - holder.image?.setImageDrawable(getIconRes(playlist)) + holder.image?.setImageDrawable(getIconRes()) val isChecked = isChecked(playlist) if (isChecked) { holder.menu?.hide() @@ -92,37 +91,25 @@ class PlaylistAdapter( //PlaylistBitmapLoader(this, holder, playlist).execute() } - private fun getIconRes(playlist: Playlist): Drawable { - return if (MusicUtil.isFavoritePlaylist(activity, playlist)) - TintHelper.createTintedDrawable( - activity, - R.drawable.ic_favorite, - ThemeStore.accentColor(activity) - ) - else TintHelper.createTintedDrawable( - activity, - R.drawable.ic_playlist_play, - ATHUtil.resolveColor(activity, R.attr.colorControlNormal) - ) - } - - override fun getItemViewType(position: Int): Int { - return if (dataSet[position] is AbsSmartPlaylist) SMART_PLAYLIST else DEFAULT_PLAYLIST - } + private fun getIconRes(): Drawable = TintHelper.createTintedDrawable( + activity, + R.drawable.ic_playlist_play, + ATHUtil.resolveColor(activity, R.attr.colorControlNormal) + ) override fun getItemCount(): Int { return dataSet.size } - override fun getIdentifier(position: Int): Playlist? { + override fun getIdentifier(position: Int): PlaylistWithSongs? { return dataSet[position] } - override fun getName(playlist: Playlist): String { - return playlist.name + override fun getName(playlist: PlaylistWithSongs): String { + return playlist.playlistEntity.playlistName } - override fun onMultipleItemAction(menuItem: MenuItem, selection: List) { + override fun onMultipleItemAction(menuItem: MenuItem, selection: List) { when (menuItem.itemId) { else -> SongsMenuHelper.handleMenuClick( activity, @@ -132,37 +119,26 @@ class PlaylistAdapter( } } - private fun getSongList(playlists: List): List { - val songs = ArrayList() - for (playlist in playlists) { - if (playlist is AbsCustomPlaylist) { - songs.addAll(playlist.songs()) - } else { - songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id)) - } + private fun getSongList(playlists: List): List { + val songs = mutableListOf() + playlists.forEach { + songs.addAll(it.songs.toSongs()) } return songs } - private fun getSongs(playlist: Playlist): List { - val songs = ArrayList() - if (playlist is AbsSmartPlaylist) { - songs.addAll(playlist.songs()) - } else { - songs.addAll(playlist.getSongs()) + private fun getSongs(playlist: PlaylistWithSongs): List = + mutableListOf().apply { + addAll(playlist.songs) } - return songs - } inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { init { - image?.apply { val iconPadding = activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding) setPadding(iconPadding, iconPadding, iconPadding, iconPadding) } - menu?.setOnClickListener { view -> val popupMenu = PopupMenu(activity, view) popupMenu.inflate(R.menu.menu_item_playlist) @@ -221,7 +197,5 @@ class PlaylistAdapter( companion object { val TAG: String = PlaylistAdapter::class.java.simpleName - private const val SMART_PLAYLIST = 0 - private const val DEFAULT_PLAYLIST = 1 } } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt index 2b3ccc3c..6407a626 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/OrderablePlaylistSongAdapter.kt @@ -5,7 +5,9 @@ import android.view.View import androidx.fragment.app.FragmentActivity import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R.menu -import code.name.monkey.retromusic.dialogs.RemoveFromPlaylistDialog +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.toSongs +import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.model.Song @@ -16,6 +18,7 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags class OrderablePlaylistSongAdapter( + private val playlist: PlaylistEntity, activity: FragmentActivity, dataSet: ArrayList, itemLayoutRes: Int, @@ -54,8 +57,8 @@ class OrderablePlaylistSongAdapter( override fun onMultipleItemAction(menuItem: MenuItem, selection: List) { when (menuItem.itemId) { R.id.action_remove_from_playlist -> { - RemoveFromPlaylistDialog.create(selection as ArrayList) - .show(activity.supportFragmentManager, "ADD_PLAYLIST") + RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId)) + .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") return } } @@ -118,7 +121,7 @@ class OrderablePlaylistSongAdapter( override fun onSongMenuItemClick(item: MenuItem): Boolean { when (item.itemId) { R.id.action_remove_from_playlist -> { - RemoveFromPlaylistDialog.create(song as PlaylistSong) + RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId)) .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") return true } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt index b699b39c..2b1b9198 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/song/PlayingQueueAdapter.kt @@ -20,7 +20,6 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstant import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem -import com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults import me.zhanghai.android.fastscroll.PopupTextProvider class PlayingQueueAdapter( @@ -153,8 +152,8 @@ class PlayingQueueAdapter( mDragStateFlags = flags } - override fun getSwipeableContainerView(): View? { - return dummyContainer + override fun getSwipeableContainerView(): View { + return dummyContainer!! } } @@ -165,18 +164,15 @@ class PlayingQueueAdapter( private const val UP_NEXT = 2 } - override fun onSwipeItem( - holder: ViewHolder?, - position: Int, @SwipeableItemResults result: Int - ): SwipeResultAction { - return if (result === SwipeableItemConstants.RESULT_CANCELED) { + override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? { + return if (result == SwipeableItemConstants.RESULT_CANCELED) { SwipeResultActionDefault() } else { SwipedResultActionRemoveItem(this, position, activity) } } - override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int { + override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int { return if (onCheckCanStartDrag(holder!!, position, x, y)) { SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H } else { @@ -184,10 +180,10 @@ class PlayingQueueAdapter( } } - override fun onSwipeItemStarted(p0: ViewHolder?, p1: Int) { + override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) { } - override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) { + override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) { } internal class SwipedResultActionRemoveItem( diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt new file mode 100644 index 00000000..db0dd0f6 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreDao.kt @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.db + +import androidx.room.* + +@Dao +interface BlackListStoreDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertBlacklistPath(blackListStoreEntities: List) + + @Delete + suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity) + + @Query("DELETE FROM BlackListStoreEntity") + suspend fun clearBlacklist() + + @Query("SELECT * FROM BlackListStoreEntity") + fun blackListPaths(): List +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt new file mode 100644 index 00000000..5ccbce07 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/BlackListStoreEntity.kt @@ -0,0 +1,10 @@ +package code.name.monkey.retromusic.db + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +class BlackListStoreEntity( + @PrimaryKey + val path: String +) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt new file mode 100644 index 00000000..5ea731a2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryDao.kt @@ -0,0 +1,26 @@ +package code.name.monkey.retromusic.db + +import androidx.lifecycle.LiveData +import androidx.room.* + +@Dao +interface HistoryDao { + companion object { + private const val HISTORY_LIMIT = 100 + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertSongInHistory(historyEntity: HistoryEntity) + + @Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1") + suspend fun isSongPresentInHistory(songId: Int): HistoryEntity? + + @Update + suspend fun updateHistorySong(historyEntity: HistoryEntity) + + @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT") + fun historySongs(): List + + @Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT") + fun observableHistorySongs(): LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt new file mode 100644 index 00000000..bbb2471f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/HistoryEntity.kt @@ -0,0 +1,32 @@ +package code.name.monkey.retromusic.db + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +class HistoryEntity( + @PrimaryKey + val id: Int, + val title: String, + @ColumnInfo(name = "track_number") + val trackNumber: Int, + val year: Int, + val duration: Long, + val data: String, + @ColumnInfo(name = "date_modified") + val dateModified: Long, + @ColumnInfo(name = "album_id") + val albumId: Int, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Int, + @ColumnInfo(name = "artist_name") + val artistName: String, + val composer: String?, + @ColumnInfo(name = "album_artist") + val albumArtist: String?, + @ColumnInfo(name = "time_played") + val timePlayed: Long +) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt new file mode 100644 index 00000000..a09b430a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsDao.kt @@ -0,0 +1,18 @@ +package code.name.monkey.retromusic.db + +import androidx.room.* + +@Dao +interface LyricsDao { + @Query("SELECT * FROM LyricsEntity WHERE songId =:songId LIMIT 1") + fun lyricsWithSongId(songId: Int): LyricsEntity? + + @Insert + fun insertLyrics(lyricsEntity: LyricsEntity) + + @Delete + fun deleteLyrics(lyricsEntity: LyricsEntity) + + @Update + fun updateLyrics(lyricsEntity: LyricsEntity) +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt new file mode 100644 index 00000000..0cec6431 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/LyricsEntity.kt @@ -0,0 +1,10 @@ +package code.name.monkey.retromusic.db + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +class LyricsEntity( + @PrimaryKey val songId: Int, + val lyrics: String +) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt new file mode 100644 index 00000000..0c3fbbae --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountDao.kt @@ -0,0 +1,27 @@ +package code.name.monkey.retromusic.db + +import androidx.room.* + +@Dao +interface PlayCountDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertSongInPlayCount(playCountEntity: PlayCountEntity) + + @Update + fun updateSongInPlayCount(playCountEntity: PlayCountEntity) + + @Delete + fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) + + @Query("SELECT * FROM PlayCountEntity WHERE id =:songId") + fun checkSongExistInPlayCount(songId: Int): List + + @Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC") + fun playCountSongs(): List + + @Query("DELETE FROM SongEntity WHERE id =:songId") + fun deleteSong(songId: Int) + + @Query("UPDATE PlayCountEntity SET play_count = play_count + 1 WHERE id = :id") + fun updateQuantity(id: Int) +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt new file mode 100644 index 00000000..94d496d4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlayCountEntity.kt @@ -0,0 +1,34 @@ +package code.name.monkey.retromusic.db + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +class PlayCountEntity( + @PrimaryKey + val id: Int, + val title: String, + @ColumnInfo(name = "track_number") + val trackNumber: Int, + val year: Int, + val duration: Long, + val data: String, + @ColumnInfo(name = "date_modified") + val dateModified: Long, + @ColumnInfo(name = "album_id") + val albumId: Int, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Int, + @ColumnInfo(name = "artist_name") + val artistName: String, + val composer: String?, + @ColumnInfo(name = "album_artist") + val albumArtist: String?, + @ColumnInfo(name = "time_played") + val timePlayed: Long, + @ColumnInfo(name = "play_count") + var playCount: Int +) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt new file mode 100644 index 00000000..219ca590 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistDao.kt @@ -0,0 +1,56 @@ +package code.name.monkey.retromusic.db + +import androidx.lifecycle.LiveData +import androidx.room.* + +@Dao +interface PlaylistDao { + @Insert + suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long + + @Query("UPDATE PlaylistEntity SET playlist_name = :name WHERE playlist_id = :playlistId") + suspend fun renamePlaylist(playlistId: Int, name: String) + + @Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name") + fun isPlaylistExists(name: String): List + + @Query("SELECT * FROM PlaylistEntity") + suspend fun playlists(): List + + @Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId") + suspend fun deletePlaylistSongs(playlistId: Int) + + @Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") + suspend fun deleteSongFromPlaylist(playlistId: Int, songId: Int) + + @Transaction + @Query("SELECT * FROM PlaylistEntity") + suspend fun playlistsWithSongs(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertSongsToPlaylist(songEntities: List) + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") + suspend fun isSongExistsInPlaylist(playlistId: Int, songId: Int): List + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId") + fun songsFromPlaylist(playlistId: Int): LiveData> + + @Delete + suspend fun deletePlaylist(playlistEntity: PlaylistEntity) + + @Delete + suspend fun deletePlaylists(playlistEntities: List) + + @Delete + suspend fun deletePlaylistSongs(songs: List) + + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") + fun favoritesSongsLiveData(playlistId: Int): LiveData> + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") + fun favoritesSongs(playlistId: Int): List + + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt new file mode 100644 index 00000000..3bb1681f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistEntity.kt @@ -0,0 +1,18 @@ +package code.name.monkey.retromusic.db + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize + +@Entity +@Parcelize +class PlaylistEntity( + @ColumnInfo(name = "playlist_name") + val playlistName: String +) : Parcelable { + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "playlist_id") + var playListId: Int = 0 +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt new file mode 100644 index 00000000..5a256bde --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/PlaylistWithSongs.kt @@ -0,0 +1,17 @@ +package code.name.monkey.retromusic.db + +import android.os.Parcelable +import androidx.room.Embedded +import androidx.room.Relation +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class PlaylistWithSongs( + @Embedded val playlistEntity: PlaylistEntity, + @Relation( + parentColumn = "playlist_id", + entityColumn = "playlist_creator_id" + ) + val songs: List +) : Parcelable + diff --git a/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt new file mode 100644 index 00000000..6be545b6 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/RetroDatabase.kt @@ -0,0 +1,17 @@ +package code.name.monkey.retromusic.db + +import androidx.room.Database +import androidx.room.RoomDatabase + +@Database( + entities = [PlaylistEntity::class, SongEntity::class, HistoryEntity::class, PlayCountEntity::class, BlackListStoreEntity::class, LyricsEntity::class], + version = 22, + exportSchema = false +) +abstract class RetroDatabase : RoomDatabase() { + abstract fun playlistDao(): PlaylistDao + abstract fun blackListStore(): BlackListStoreDao + abstract fun playCountDao(): PlayCountDao + abstract fun historyDao(): HistoryDao + abstract fun lyricsDao(): LyricsDao +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt new file mode 100644 index 00000000..19a50fe0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/SongEntity.kt @@ -0,0 +1,40 @@ +package code.name.monkey.retromusic.db + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize + +@Parcelize +@Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)]) +class SongEntity( + @ColumnInfo(name = "playlist_creator_id") + val playlistCreatorId: Int, + val id: Int, + val title: String, + @ColumnInfo(name = "track_number") + val trackNumber: Int, + val year: Int, + val duration: Long, + val data: String, + @ColumnInfo(name = "date_modified") + val dateModified: Long, + @ColumnInfo(name = "album_id") + val albumId: Int, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Int, + @ColumnInfo(name = "artist_name") + val artistName: String, + val composer: String?, + @ColumnInfo(name = "album_artist") + val albumArtist: String? +) : Parcelable { + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "song_key") + var songPrimaryKey: Long = 0 +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt new file mode 100644 index 00000000..d5f009e0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/db/SongExtension.kt @@ -0,0 +1,95 @@ +package code.name.monkey.retromusic.db + +import code.name.monkey.retromusic.model.Song + +fun List.toSongs(): List { + return map { + it.toSong() + } +} + +fun List.toSongs(playlistId: Int): List { + return map { + it.toSongEntity(playlistId) + } +} + +fun SongEntity.toSong(): Song { + return Song( + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist + ) +} + +fun PlayCountEntity.toSong(): Song { + return Song( + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist + ) +} + +fun HistoryEntity.toSong(): Song { + return Song( + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist + ) +} + +fun Song.toPlayCount(): PlayCountEntity { + return PlayCountEntity( + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist, + System.currentTimeMillis(), + 1 + ) +} + +fun List.toSongsEntity(playlistEntity: PlaylistEntity): List { + return map { + it.toSongEntity(playlistEntity.playListId) + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt index c4d54ef0..53acaa25 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.kt @@ -1,77 +1,70 @@ -/* - * 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.dialogs import android.app.Dialog import android.os.Bundle +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope +import code.name.monkey.retromusic.EXTRA_PLAYLISTS import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.SongEntity +import code.name.monkey.retromusic.db.toSongsEntity import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType.Playlists import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.repository.PlaylistRepository -import code.name.monkey.retromusic.util.PlaylistsUtil -import org.koin.android.ext.android.inject - +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class AddToPlaylistDialog : DialogFragment() { - private val playlistRepository by inject() - override fun onCreateDialog( - savedInstanceState: Bundle? - ): Dialog { - val playlists = playlistRepository.playlists() - val playlistNames = mutableListOf() + private val libraryViewModel by sharedViewModel() + + companion object { + fun create(playlistEntities: List, song: Song): AddToPlaylistDialog { + val list: MutableList = mutableListOf() + list.add(song) + return create(playlistEntities, list) + } + + fun create(playlistEntities: List, songs: List): AddToPlaylistDialog { + return AddToPlaylistDialog().apply { + arguments = bundleOf( + EXTRA_SONG to songs, + EXTRA_PLAYLISTS to playlistEntities + ) + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val playlistEntities: List = + extraNotNull>(EXTRA_PLAYLISTS).value + val songs: List = extraNotNull>(EXTRA_SONG).value + val playlistNames: MutableList = mutableListOf() playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist)) - for (p in playlists) { - playlistNames.add(p.name) + for (entity: PlaylistEntity in playlistEntities) { + playlistNames.add(entity.playlistName) } return materialDialog(R.string.add_playlist_title) .setItems(playlistNames.toTypedArray()) { _, which -> - val songs = extraNotNull>(EXTRA_SONG).value if (which == 0) { CreatePlaylistDialog.create(songs) - .show(requireActivity().supportFragmentManager, "ADD_TO_PLAYLIST") + .show(requireActivity().supportFragmentManager, "Dialog") } else { - PlaylistsUtil.addToPlaylist( - requireContext(), - songs, - playlists[which - 1].id, - true - ) + lifecycleScope.launch(Dispatchers.IO) { + val songEntities: List = + songs.toSongsEntity(playlistEntities[which - 1]) + libraryViewModel.insertSongs(songEntities) + libraryViewModel.forceReload(Playlists) + } } dismiss() } .create().colorButtons() } - - companion object { - - fun create(song: Song): AddToPlaylistDialog { - val list = ArrayList() - list.add(song) - return create(list) - } - - fun create(songs: List): AddToPlaylistDialog { - val dialog = AddToPlaylistDialog() - val args = Bundle() - args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs)) - dialog.arguments = args - return dialog - } - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt index d4eeb9eb..db5158ae 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt @@ -1,88 +1,74 @@ -/* - * 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.dialogs -import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle import android.text.TextUtils import android.view.LayoutInflater +import android.widget.Toast +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment -import code.name.monkey.appthemehelper.util.MaterialUtil +import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.extensions.colorButtons -import code.name.monkey.retromusic.extensions.extraNotNull +import code.name.monkey.retromusic.extensions.extra import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType.Playlists import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.util.PlaylistsUtil import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import kotlinx.android.synthetic.main.dialog_playlist.view.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class CreatePlaylistDialog : DialogFragment() { + private val libraryViewModel by sharedViewModel() - @SuppressLint("InflateParams") - override fun onCreateDialog( - savedInstanceState: Bundle? - ): Dialog { + companion object { + fun create(song: Song): CreatePlaylistDialog { + val list = mutableListOf() + list.add(song) + return create(list) + } + + fun create(songs: List): CreatePlaylistDialog { + return CreatePlaylistDialog().apply { + arguments = bundleOf(EXTRA_SONG to songs) + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null) + val songs: List = extra>(EXTRA_SONG).value ?: emptyList() val playlistView: TextInputEditText = view.actionNewPlaylist val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer - MaterialUtil.setTint(playlistContainer, false) - return materialDialog(R.string.new_playlist_title) .setView(view) - .setNegativeButton(android.R.string.cancel, null) .setPositiveButton( R.string.create_action ) { _, _ -> - val extra = extraNotNull>(EXTRA_SONG) val playlistName = playlistView.text.toString() if (!TextUtils.isEmpty(playlistName)) { - val playlistId = PlaylistsUtil.createPlaylist( - requireContext(), - playlistView.text.toString() - ) - if (playlistId != -1) { - PlaylistsUtil.addToPlaylist(requireContext(), extra.value, playlistId, true) + lifecycleScope.launch(Dispatchers.IO) { + if (libraryViewModel.checkPlaylistExists(playlistName).isEmpty()) { + val playlistId: Long = + libraryViewModel.createPlaylist(PlaylistEntity(playlistName)) + libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId.toInt()) }) + libraryViewModel.forceReload(Playlists) + } else { + Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT) + .show() + } } + } else { + playlistContainer.error = "Playlist is can't be empty" } } .create() .colorButtons() } - - companion object { - @JvmOverloads - @JvmStatic - fun create(song: Song? = null): CreatePlaylistDialog { - val list = ArrayList() - if (song != null) { - list.add(song) - } - return create(list) - } - - @JvmStatic - fun create(songs: ArrayList): CreatePlaylistDialog { - val dialog = CreatePlaylistDialog() - val args = Bundle() - args.putParcelableArrayList(EXTRA_SONG, songs) - dialog.arguments = args - return dialog - } - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt index 7a548de4..1ee8953a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt @@ -1,35 +1,41 @@ -/* - * 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.dialogs import android.app.Dialog import android.os.Bundle +import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat import androidx.fragment.app.DialogFragment import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog -import code.name.monkey.retromusic.model.Playlist -import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class DeletePlaylistDialog : DialogFragment() { + private val libraryViewModel by sharedViewModel() + + companion object { + + fun create(playlist: PlaylistEntity): DeletePlaylistDialog { + val list = mutableListOf() + list.add(playlist) + return create(list) + } + + fun create(playlists: List): DeletePlaylistDialog { + return DeletePlaylistDialog().apply { + arguments = bundleOf(EXTRA_PLAYLIST to playlists) + } + } + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val playlists = extraNotNull>(EXTRA_PLAYLIST).value + val playlists = extraNotNull>(EXTRA_PLAYLIST).value val title: Int val message: CharSequence //noinspection ConstantConditions @@ -42,7 +48,7 @@ class DeletePlaylistDialog : DialogFragment() { } else { title = R.string.delete_playlist_title message = HtmlCompat.fromHtml( - String.format(getString(R.string.delete_playlist_x), playlists[0].name), + String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName), HtmlCompat.FROM_HTML_MODE_LEGACY ) } @@ -52,26 +58,12 @@ class DeletePlaylistDialog : DialogFragment() { .setMessage(message) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.action_delete) { _, _ -> - PlaylistsUtil.deletePlaylists(requireContext(), playlists) + libraryViewModel.deleteSongsFromPlaylist(playlists) + libraryViewModel.deleteRoomPlaylist(playlists) + libraryViewModel.forceReload(ReloadType.Playlists) } .create() .colorButtons() } - companion object { - - fun create(playlist: Playlist): DeletePlaylistDialog { - val list = ArrayList() - list.add(playlist) - return create(list) - } - - fun create(playlist: ArrayList): DeletePlaylistDialog { - val dialog = DeletePlaylistDialog() - val args = Bundle() - args.putParcelableArrayList(EXTRA_PLAYLIST, playlist) - dialog.arguments = args - return dialog - } - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsAsyncTask.java deleted file mode 100644 index 5cc00d76..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsAsyncTask.java +++ /dev/null @@ -1,145 +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.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; - -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.activities.saf.SAFGuideActivity; -import code.name.monkey.retromusic.misc.DialogAsyncTask; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.util.SAFUtil; - -/** - * Created by hemanths on 2019-07-31. - */ -public class DeleteSongsAsyncTask extends DialogAsyncTask { - - private WeakReference activityWeakReference; - private WeakReference dialogReference; - - public DeleteSongsAsyncTask(@NonNull DeleteSongsDialog dialog) { - super(dialog.getActivity()); - this.dialogReference = new WeakReference<>(dialog); - this.activityWeakReference = new WeakReference<>(dialog.getActivity()); - } - - @NonNull - @Override - protected Dialog createDialog(@NonNull Context context) { - return new MaterialAlertDialogBuilder(context, - R.style.ThemeOverlay_MaterialComponents_Dialog_Alert) - .setTitle(R.string.deleting_songs) - .setView(R.layout.loading) - .setCancelable(false) - .create(); - } - - @Nullable - @Override - protected Void doInBackground(@NonNull LoadingInfo... loadingInfos) { - try { - LoadingInfo info = loadingInfos[0]; - DeleteSongsDialog dialog = this.dialogReference.get(); - FragmentActivity fragmentActivity = this.activityWeakReference.get(); - - if (dialog == null || fragmentActivity == null) { - return null; - } - - if (!info.isIntent) { - if (!SAFUtil.isSAFRequiredForSongs(info.songs)) { - dialog.deleteSongs(info.songs, null); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (SAFUtil.isSDCardAccessGranted(fragmentActivity)) { - dialog.deleteSongs(info.songs, null); - } else { - dialog.startActivityForResult(new Intent(fragmentActivity, SAFGuideActivity.class), - SAFGuideActivity.REQUEST_CODE_SAF_GUIDE); - } - } else { - Log.i("Hmm", "doInBackground: kitkat delete songs"); - } - } - } else { - switch (info.requestCode) { - case SAFUtil.REQUEST_SAF_PICK_TREE: - if (info.resultCode == Activity.RESULT_OK) { - SAFUtil.saveTreeUri(fragmentActivity, info.intent); - if (dialog.songsToRemove != null) { - dialog.deleteSongs(dialog.songsToRemove, null); - } - } - break; - case SAFUtil.REQUEST_SAF_PICK_FILE: - if (info.resultCode == Activity.RESULT_OK) { - dialog.deleteSongs(Collections.singletonList(dialog.currentSong), - Collections.singletonList(info.intent.getData())); - } - break; - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - public static class LoadingInfo { - - public Intent intent; - - public boolean isIntent; - - public int requestCode; - - public int resultCode; - - public List safUris; - - public List songs; - - public LoadingInfo(List songs, List safUris) { - this.isIntent = false; - this.songs = songs; - this.safUris = safUris; - } - - public LoadingInfo(int requestCode, int resultCode, Intent intent) { - this.isIntent = true; - this.requestCode = requestCode; - this.resultCode = resultCode; - this.intent = intent; - } - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt index 1765d232..24a67a0f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt @@ -1,110 +1,24 @@ -/* - * 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.dialogs import android.app.Dialog -import android.content.Intent -import android.net.Uri import android.os.Bundle import androidx.core.text.HtmlCompat import androidx.fragment.app.DialogFragment import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.activities.saf.SAFGuideActivity import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.MusicUtil -import code.name.monkey.retromusic.util.SAFUtil +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class DeleteSongsDialog : DialogFragment() { - @JvmField - var currentSong: Song? = null - - @JvmField - var songsToRemove: List? = null - - private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val songs = extraNotNull>(EXTRA_SONG).value - var title = 0 - var message: CharSequence = "" - if (songs.size > 1) { - title = R.string.delete_songs_title - message = HtmlCompat.fromHtml( - String.format(getString(R.string.delete_x_songs), songs.size), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - } else { - title = R.string.delete_song_title - message = HtmlCompat.fromHtml( - String.format(getString(R.string.delete_song_x), songs[0].title), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - } - - return materialDialog(title) - .setMessage(message) - .setCancelable(false) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.action_delete) { _, _ -> - if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) { - MusicPlayerRemote.playNextSong() - } - songsToRemove = songs - deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog) - deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null)) - } - .create() - .colorButtons() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - when (requestCode) { - SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> { - SAFUtil.openTreePicker(this) - } - SAFUtil.REQUEST_SAF_PICK_TREE, - SAFUtil.REQUEST_SAF_PICK_FILE -> { - if (deleteSongsAsyncTask != null) { - deleteSongsAsyncTask?.cancel(true) - } - deleteSongsAsyncTask = DeleteSongsAsyncTask(this) - deleteSongsAsyncTask?.execute( - DeleteSongsAsyncTask.LoadingInfo( - requestCode, - resultCode, - data - ) - ) - } - } - } - - fun deleteSongs(songs: List, safUris: List?) { - MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable { - dismiss() - }) - } + private val libraryViewModel by sharedViewModel() companion object { - fun create(song: Song): DeleteSongsDialog { val list = ArrayList() list.add(song) @@ -119,5 +33,38 @@ class DeleteSongsDialog : DialogFragment() { return dialog } } -} + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val songs = extraNotNull>(EXTRA_SONG).value + val pair = if (songs.size > 1) { + Pair( + R.string.delete_songs_title, + HtmlCompat.fromHtml( + String.format(getString(R.string.delete_x_songs), songs.size), + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) + } else { + Pair( + R.string.delete_song_title, + HtmlCompat.fromHtml( + String.format(getString(R.string.delete_song_x), songs[0].title), + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) + } + + return materialDialog(pair.first) + .setMessage(pair.second) + .setCancelable(false) + .setPositiveButton(R.string.action_delete) { _, _ -> + if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) { + MusicPlayerRemote.playNextSong() + } + MusicUtil.deleteTracks(requireActivity(), songs) + libraryViewModel.deleteTracks(songs) + } + .create() + .colorButtons() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt new file mode 100644 index 00000000..359ef1c5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ImportPlaylistDialog.kt @@ -0,0 +1,24 @@ +package code.name.monkey.retromusic.dialogs + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel +import org.koin.androidx.viewmodel.ext.android.sharedViewModel + +class ImportPlaylistDialog : DialogFragment() { + private val libraryViewModel by sharedViewModel() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return materialDialog(R.string.import_playlist) + .setMessage(R.string.import_playlist_message) + .setPositiveButton(R.string.import_label) { _, _ -> + libraryViewModel.importPlaylists() + } + .create() + .colorButtons() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/LyricsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/LyricsDialog.kt new file mode 100644 index 00000000..9c3b63c7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/LyricsDialog.kt @@ -0,0 +1,60 @@ +package code.name.monkey.retromusic.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.extensions.accentTextColor +import code.name.monkey.retromusic.extensions.hide +import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.network.Result +import code.name.monkey.retromusic.repository.Repository +import kotlinx.android.synthetic.main.lyrics_dialog.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.inject + +class LyricsDialog : DialogFragment() { + override fun getTheme(): Int { + return R.style.MaterialAlertDialogTheme + } + + private val repository by inject() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.lyrics_dialog, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val song = MusicPlayerRemote.currentSong + dialogTitle.text = song.title + syncedLyrics.accentTextColor() + lifecycleScope.launch(IO) { + val result: Result = repository.lyrics( + song.artistName, + song.title + ) + withContext(Main) { + + when (result) { + is Result.Error -> progressBar.hide() + is Result.Loading -> println("Loading") + is Result.Success -> { + progressBar.hide() + lyricsText.text = result.data + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt deleted file mode 100644 index dc193398..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt +++ /dev/null @@ -1,84 +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.dialogs - -import android.app.Dialog -import android.os.Bundle -import androidx.core.text.HtmlCompat -import androidx.fragment.app.DialogFragment -import code.name.monkey.retromusic.EXTRA_SONG -import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.R.string -import code.name.monkey.retromusic.extensions.colorButtons -import code.name.monkey.retromusic.extensions.materialDialog -import code.name.monkey.retromusic.model.PlaylistSong -import code.name.monkey.retromusic.util.PlaylistsUtil - -class RemoveFromPlaylistDialog : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val songs = requireArguments().getParcelableArrayList(EXTRA_SONG) - - var title = 0 - var message: CharSequence = "" - if (songs != null) { - if (songs.size > 1) { - title = R.string.remove_songs_from_playlist_title - message = HtmlCompat.fromHtml( - String.format(getString(string.remove_x_songs_from_playlist), songs.size), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - } else { - title = R.string.remove_song_from_playlist_title - message = HtmlCompat.fromHtml( - String.format( - getString(string.remove_song_x_from_playlist), - songs[0].title - ), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - } - } - - return materialDialog(title) - .setMessage(message) - .setPositiveButton(R.string.remove_action) { _, _ -> - PlaylistsUtil.removeFromPlaylist( - requireContext(), - songs as MutableList - ) - } - .setNegativeButton(android.R.string.cancel, null) - .create() - .colorButtons() - } - - companion object { - - fun create(song: PlaylistSong): RemoveFromPlaylistDialog { - val list = ArrayList() - list.add(song) - return create(list) - } - - fun create(songs: ArrayList): RemoveFromPlaylistDialog { - val dialog = RemoveFromPlaylistDialog() - val args = Bundle() - args.putParcelableArrayList(EXTRA_SONG, songs) - dialog.arguments = args - return dialog - } - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt new file mode 100644 index 00000000..b52e9fef --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt @@ -0,0 +1,69 @@ +package code.name.monkey.retromusic.dialogs + +import android.app.Dialog +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.core.text.HtmlCompat +import androidx.fragment.app.DialogFragment +import code.name.monkey.retromusic.EXTRA_SONG +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.SongEntity +import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.extensions.extraNotNull +import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType.Playlists +import org.koin.androidx.viewmodel.ext.android.sharedViewModel + +class RemoveSongFromPlaylistDialog : DialogFragment() { + private val libraryViewModel by sharedViewModel() + + companion object { + fun create(song: SongEntity): RemoveSongFromPlaylistDialog { + val list = mutableListOf() + list.add(song) + return create(list) + } + + fun create(songs: List): RemoveSongFromPlaylistDialog { + return RemoveSongFromPlaylistDialog().apply { + arguments = bundleOf( + EXTRA_SONG to songs + ) + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val songs = extraNotNull>(EXTRA_SONG).value + val pair = if (songs.size > 1) { + Pair( + R.string.remove_songs_from_playlist_title, + HtmlCompat.fromHtml( + String.format(getString(R.string.remove_x_songs_from_playlist), songs.size), + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) + } else { + Pair( + R.string.remove_song_from_playlist_title, + HtmlCompat.fromHtml( + String.format( + getString(R.string.remove_song_x_from_playlist), + songs[0].title + ), + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) + } + return materialDialog(pair.first) + .setMessage(pair.second) + .setPositiveButton(R.string.remove_action) { _, _ -> + libraryViewModel.deleteSongsInPlaylist(songs) + libraryViewModel.forceReload(Playlists) + } + .setNegativeButton(android.R.string.cancel, null) + .create() + .colorButtons() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt index 78912d2a..6aa6939a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt @@ -1,73 +1,57 @@ -/* - * 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.dialogs -import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle -import android.provider.MediaStore.Audio.Playlists.Members.PLAYLIST_ID import android.view.LayoutInflater +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment -import code.name.monkey.appthemehelper.util.MaterialUtil +import code.name.monkey.retromusic.EXTRA_PLAYLIST_ID import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog -import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class RenamePlaylistDialog : DialogFragment() { + private val libraryViewModel by sharedViewModel() - @SuppressLint("InflateParams") - override fun onCreateDialog( - savedInstanceState: Bundle? - ): Dialog { - val layout = LayoutInflater.from(requireContext()) - .inflate(R.layout.dialog_playlist, null) + companion object { + fun create(playlistEntity: PlaylistEntity): RenamePlaylistDialog { + return RenamePlaylistDialog().apply { + arguments = bundleOf( + EXTRA_PLAYLIST_ID to playlistEntity + ) + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val playlistEntity = extraNotNull(EXTRA_PLAYLIST_ID).value + val layout = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_playlist, null) val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist) - val nameContainer: TextInputLayout = - layout.findViewById(R.id.actionNewPlaylistContainer) - MaterialUtil.setTint(nameContainer, false) - + val nameContainer: TextInputLayout = layout.findViewById(R.id.actionNewPlaylistContainer) + nameContainer.accentColor() + inputEditText.setText(playlistEntity.playlistName) return materialDialog(R.string.rename_playlist_title) .setView(layout) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.action_rename) { _, _ -> val name = inputEditText.text.toString() if (name.isNotEmpty()) { - PlaylistsUtil.renamePlaylist( - requireContext(), - extraNotNull(PLAYLIST_ID).value, - name - ) + libraryViewModel.renameRoomPlaylist(playlistEntity.playListId, name) + libraryViewModel.forceReload(ReloadType.Playlists) + } else { + nameContainer.error = "Playlist name should'nt be empty" } } .create() .colorButtons() } - - companion object { - - fun create(playlistId: Long): RenamePlaylistDialog { - val dialog = RenamePlaylistDialog() - val args = Bundle() - args.putLong(PLAYLIST_ID, playlistId) - dialog.arguments = args - return dialog - } - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RetroSingleCheckedListAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RetroSingleCheckedListAdapter.kt deleted file mode 100644 index c8fa03cd..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RetroSingleCheckedListAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package code.name.monkey.retromusic.dialogs - -import android.content.Context -import android.widget.ArrayAdapter -import code.name.monkey.retromusic.R - -class RetroSingleCheckedListAdapter( - context: Context, - resource: Int = R.layout.dialog_list_item, - objects: MutableList -) : ArrayAdapter(context, resource, objects) { - -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt new file mode 100644 index 00000000..dd92a9a2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt @@ -0,0 +1,55 @@ +package code.name.monkey.retromusic.dialogs + +import android.app.Dialog +import android.os.Bundle +import android.widget.Toast +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope +import code.name.monkey.retromusic.App +import code.name.monkey.retromusic.EXTRA_PLAYLIST +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.extensions.extraNotNull +import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.util.PlaylistsUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + + +class SavePlaylistDialog : DialogFragment() { + companion object { + fun create(playlistWithSongs: PlaylistWithSongs): SavePlaylistDialog { + return SavePlaylistDialog().apply { + arguments = bundleOf( + EXTRA_PLAYLIST to playlistWithSongs + ) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launch(Dispatchers.IO) { + val playlistWithSongs: PlaylistWithSongs = + extraNotNull(EXTRA_PLAYLIST).value + val file = PlaylistsUtil.savePlaylistWithSongs(requireContext(), playlistWithSongs) + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + String.format(App.getContext().getString(R.string.saved_playlist_to), file), + Toast.LENGTH_LONG + ).show() + dismiss() + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return materialDialog(R.string.save_playlist_title) + .setView(R.layout.loading) + .create().colorButtons() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt index 466ec532..21c43f30 100755 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt @@ -53,8 +53,8 @@ class SleepTimerDialog : DialogFragment() { @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { timerUpdater = TimerUpdater() - val layout = LayoutInflater.from(requireContext()) - .inflate(R.layout.dialog_sleep_timer, null) + val layout = + LayoutInflater.from(requireContext()).inflate(R.layout.dialog_sleep_timer, null) shouldFinishLastSong = layout.findViewById(R.id.shouldFinishLastSong) seekBar = layout.findViewById(R.id.seekBar) timerDisplay = layout.findViewById(R.id.timerDisplay) @@ -158,7 +158,7 @@ class SleepTimerDialog : DialogFragment() { } } - private inner class TimerUpdater internal constructor() : + private inner class TimerUpdater() : CountDownTimer( PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(), 1000 diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt index d6228dc0..3409b698 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.kt @@ -21,8 +21,6 @@ import android.os.Bundle import android.text.Spanned import android.util.Log import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import android.widget.TextView import androidx.annotation.NonNull import androidx.core.text.HtmlCompat @@ -41,12 +39,6 @@ import org.jaudiotagger.tag.TagException import java.io.File import java.io.IOException -inline fun ViewGroup.forEach(action: (View) -> Unit) { - for (i in 0 until childCount) { - action(getChildAt(i)) - } -} - class SongDetailDialog : DialogFragment() { @SuppressLint("InflateParams") diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt index 615a8acb..843cae01 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ActivityEx.kt @@ -15,13 +15,8 @@ package code.name.monkey.retromusic.extensions import android.app.Activity -import androidx.annotation.IdRes import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentTransaction import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper -import code.name.monkey.retromusic.R import com.google.android.material.appbar.MaterialToolbar fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) { @@ -30,41 +25,6 @@ fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) { setSupportActionBar(toolbar) } -fun FragmentActivity?.addFragment( - @IdRes idRes: Int = R.id.container, - fragment: Fragment, - tag: String? = null, - addToBackStack: Boolean = false -) { - val compatActivity = this as? AppCompatActivity ?: return - compatActivity.supportFragmentManager.beginTransaction() - .apply { - add(fragment, tag) - setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - if (addToBackStack) { - addToBackStack(null) - } - commitNow() - } -} - -fun AppCompatActivity.replaceFragment( - @IdRes id: Int = R.id.container, - fragment: Fragment, - tag: String? = null, - addToBackStack: Boolean = false -) { - val compatActivity = this ?: return - compatActivity.supportFragmentManager.beginTransaction() - .apply { - replace(id, fragment, tag) - if (addToBackStack) { - addToBackStack(null) - } - commit() - } -} - inline fun Activity.extra(key: String, default: T? = null) = lazy { val value = intent?.extras?.get(key) if (value is T) value else default diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt index cee067c2..9afbd431 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ColorExt.kt @@ -18,12 +18,18 @@ import android.app.Dialog import android.content.Context import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.drawable.Drawable import android.widget.Button import android.widget.CheckBox import android.widget.SeekBar import androidx.annotation.AttrRes +import androidx.annotation.CheckResult import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import androidx.fragment.app.Fragment import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ATHUtil @@ -33,6 +39,8 @@ import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import com.google.android.material.button.MaterialButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.progressindicator.ProgressIndicator import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout @@ -91,6 +99,10 @@ fun Button.accentTextColor() { setTextColor(ThemeStore.accentColor(App.getContext())) } +fun MaterialButton.accentTextColor() { + setTextColor(ThemeStore.accentColor(App.getContext())) +} + fun SeekBar.applyColor(@ColorInt color: Int) { thumbTintList = ColorStateList.valueOf(color) progressTintList = ColorStateList.valueOf(color) @@ -107,6 +119,15 @@ fun ExtendedFloatingActionButton.accentColor() { iconTint = textColorStateList } +fun FloatingActionButton.accentColor() { + val color = ThemeStore.accentColor(context) + val textColor = MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color)) + val colorStateList = ColorStateList.valueOf(color) + val textColorStateList = ColorStateList.valueOf(textColor) + backgroundTintList = colorStateList + imageTintList = textColorStateList +} + fun MaterialButton.applyColor(color: Int) { val backgroundColorStateList = ColorStateList.valueOf(color) val textColorColorStateList = ColorStateList.valueOf( @@ -120,6 +141,12 @@ fun MaterialButton.applyColor(color: Int) { iconTint = textColorColorStateList } +fun MaterialButton.applyOutlineColor(color: Int) { + val textColorColorStateList = ColorStateList.valueOf(color) + setTextColor(textColorColorStateList) + iconTint = textColorColorStateList +} + fun TextInputLayout.accentColor() { val accentColor = ThemeStore.accentColor(context) val colorState = ColorStateList.valueOf(accentColor) @@ -128,6 +155,39 @@ fun TextInputLayout.accentColor() { isHintAnimationEnabled = true } +fun ProgressIndicator.accentColor() { + val accentColor = ThemeStore.accentColor(context) + indicatorColors = intArrayOf(accentColor) + trackColor = ColorUtil.withAlpha(accentColor, 0.2f) +} + +fun ProgressIndicator.applyColor(color: Int) { + indicatorColors = intArrayOf(color) + trackColor = ColorUtil.withAlpha(color, 0.2f) +} + fun TextInputEditText.accentColor() { -} \ No newline at end of file +} + +fun AppCompatImageView.accentColor(): Int { + return ThemeStore.accentColor(context) +} + +@CheckResult +fun Drawable.tint(@ColorInt color: Int): Drawable { + val tintedDrawable = DrawableCompat.wrap(this).mutate() + DrawableCompat.setTint(this, color) + return tintedDrawable +} + +@CheckResult +fun Drawable.tint(context: Context, @ColorRes color: Int): Drawable { + return tint(context.getColorCompat(color)) +} + +@ColorInt +fun Context.getColorCompat(@ColorRes colorRes: Int): Int { + return ContextCompat.getColor(this, colorRes) +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt index d2c38310..c56a4045 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/DialogExtension.kt @@ -8,7 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder fun DialogFragment.materialDialog(title: Int): MaterialAlertDialogBuilder { return MaterialAlertDialogBuilder( requireContext(), - R.style.ThemeOverlay_MaterialComponents_Dialog_Alert + R.style.MaterialAlertDialogTheme ).setTitle(title) } diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt index 8f53f469..a826d153 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/FragmentExt.kt @@ -2,12 +2,15 @@ package code.name.monkey.retromusic.extensions import android.content.Context import android.content.res.Configuration +import android.graphics.drawable.Drawable import android.os.PowerManager import android.widget.Toast +import androidx.annotation.DrawableRes import androidx.annotation.IdRes import androidx.annotation.IntegerRes import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.navigation.fragment.NavHostFragment @@ -51,7 +54,8 @@ val FragmentManager.currentNavigationFragment: Fragment? get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? { - val navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(navHostId) as NavHostFragment + val navHostFragment: NavHostFragment = + supportFragmentManager.findFragmentById(navHostId) as NavHostFragment navHostFragment.targetFragment return navHostFragment.childFragmentManager.fragments.first() } @@ -72,4 +76,12 @@ fun Fragment.showToast(@StringRes stringRes: Int) { fun Fragment.showToast(message: String) { Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() +} + +fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { + return AppCompatResources.getDrawable(this, drawableRes)!! +} + +fun Fragment.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { + return AppCompatResources.getDrawable(requireContext(), drawableRes)!! } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt index da321935..0f9e30e8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/ViewExtensions.kt @@ -14,13 +14,10 @@ package code.name.monkey.retromusic.extensions -import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText -import android.widget.SeekBar -import androidx.annotation.ColorInt import androidx.annotation.LayoutRes import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.TintHelper diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt new file mode 100644 index 00000000..9b6a5ca4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/CoroutineViewModel.kt @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.fragments + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +open class CoroutineViewModel( + private val mainDispatcher: CoroutineDispatcher +) : ViewModel() { + private val job = Job() + protected val scope = CoroutineScope(job + mainDispatcher) + + protected fun launch( + context: CoroutineContext = mainDispatcher, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit + ) = scope.launch(context, start, block) + + override fun onCleared() { + super.onCleared() + job.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt index 53273281..6c7230a5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/DetailListFragment.kt @@ -3,6 +3,8 @@ package code.name.monkey.retromusic.fragments import android.os.Bundle import android.view.View import android.widget.ImageView +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager @@ -10,15 +12,14 @@ import code.name.monkey.retromusic.* import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter +import code.name.monkey.retromusic.db.toSong import code.name.monkey.retromusic.fragments.albums.AlbumClickListener import code.name.monkey.retromusic.fragments.artists.ArtistClickListener import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist -import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.RealRepository import kotlinx.android.synthetic.main.fragment_playlist_detail.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch @@ -34,6 +35,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de super.onActivityCreated(savedInstanceState) mainActivity.setSupportActionBar(toolbar) mainActivity.hideBottomBarVisibility(false) + progressIndicator.hide() when (args.type) { TOP_ARTISTS -> { loadArtists(R.string.top_artists, TOP_ARTISTS) @@ -47,32 +49,88 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de RECENT_ALBUMS -> { loadAlbums(R.string.recent_albums, RECENT_ALBUMS) } - FAVOURITES -> { - loadFavorite() - } + FAVOURITES -> loadFavorite() + HISTORY_PLAYLIST -> loadHistory() + LAST_ADDED_PLAYLIST -> lastAddedSongs() + TOP_PLAYED_PLAYLIST -> topPlayed() } } + private fun lastAddedSongs() { + toolbar.setTitle(R.string.last_added) + val songAdapter = SongAdapter( + requireActivity(), + mutableListOf(), + R.layout.item_list, null + ) + recyclerView.apply { + adapter = songAdapter + layoutManager = linearLayoutManager() + } + lifecycleScope.launch(IO) { + val songs = repository.recentSongs() + withContext(Main) { songAdapter.swapDataSet(songs) } + } + } + + private fun topPlayed() { + toolbar.setTitle(R.string.my_top_tracks) + val songAdapter = SongAdapter( + requireActivity(), + mutableListOf(), + R.layout.item_list, null + ) + recyclerView.apply { + adapter = songAdapter + layoutManager = linearLayoutManager() + } + lifecycleScope.launch(IO) { + val songs = repository.playCountSongs().map { + it.toSong() + } + withContext(Main) { songAdapter.swapDataSet(songs) } + } + } + + private fun loadHistory() { + toolbar.setTitle(R.string.history) + + val songAdapter = SongAdapter( + requireActivity(), + mutableListOf(), + R.layout.item_list, null + ) + recyclerView.apply { + adapter = songAdapter + layoutManager = linearLayoutManager() + } + repository.observableHistorySongs().observe(viewLifecycleOwner, Observer { + val songs = it.map { historyEntity -> historyEntity.toSong() } + songAdapter.swapDataSet(songs) + }) + } + private fun loadFavorite() { toolbar.setTitle(R.string.favorites) - CoroutineScope(IO).launch { - val songs = repository.favoritePlaylistHome() - withContext(Main) { - recyclerView.apply { - adapter = SongAdapter( - requireActivity(), - songs.arrayList as MutableList, - R.layout.item_list, null - ) - layoutManager = linearLayoutManager() - } - } + val songAdapter = SongAdapter( + requireActivity(), + mutableListOf(), + R.layout.item_list, null + ) + recyclerView.apply { + adapter = songAdapter + layoutManager = linearLayoutManager() } + repository.favorites().observe(viewLifecycleOwner, Observer { + println(it.size) + val songs = it.map { songEntity -> songEntity.toSong() } + songAdapter.swapDataSet(songs) + }) } private fun loadArtists(title: Int, type: Int) { toolbar.setTitle(title) - CoroutineScope(IO).launch { + lifecycleScope.launch(IO) { val artists = if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists() withContext(Main) { @@ -86,7 +144,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de private fun loadAlbums(title: Int, type: Int) { toolbar.setTitle(title) - CoroutineScope(IO).launch { + lifecycleScope.launch(IO) { val albums = if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums() withContext(Main) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt index dd4727a7..a60b9256 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt @@ -4,83 +4,129 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.fragments.ReloadType.* +import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.repository.RealRepository -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.async import kotlinx.coroutines.launch class LibraryViewModel( - private val realRepository: RealRepository + private val repository: RealRepository ) : ViewModel(), MusicServiceEventListener { private val paletteColor = MutableLiveData() private val albums = MutableLiveData>() private val songs = MutableLiveData>() private val artists = MutableLiveData>() - private val playlists = MutableLiveData>() + private val playlists = MutableLiveData>() + private val legacyPlaylists = MutableLiveData>() private val genres = MutableLiveData>() private val home = MutableLiveData>() val paletteColorLiveData: LiveData = paletteColor - val homeLiveData: LiveData> = home - val albumsLiveData: LiveData> = albums - val songsLiveData: LiveData> = songs - val artistsLiveData: LiveData> = artists - val playlisitsLiveData: LiveData> = playlists - val genresLiveData: LiveData> = genres init { - viewModelScope.launch { - loadLibraryContent() + fetchHomeSections() + } + + private fun loadLibraryContent() = viewModelScope.launch(IO) { + fetchHomeSections() + fetchSongs() + fetchAlbums() + fetchArtists() + fetchGenres() + fetchPlaylists() + } + + fun getSongs(): LiveData> { + fetchSongs() + return songs + } + + fun getAlbums(): LiveData> { + fetchAlbums() + return albums + } + + fun getArtists(): LiveData> { + fetchArtists() + return artists + } + + fun getPlaylists(): LiveData> { + fetchPlaylists() + return playlists + } + + fun getLegacyPlaylist(): LiveData> { + fetchLegacyPlaylist() + return legacyPlaylists + } + + fun getGenre(): LiveData> { + fetchGenres() + return genres + } + + fun getHome(): LiveData> { + return home + } + + private fun fetchSongs() { + viewModelScope.launch(IO) { + songs.postValue(repository.allSongs()) } } - private fun loadLibraryContent() = viewModelScope.launch { - songs.value = loadSongs.await() - albums.value = loadAlbums.await() - artists.value = loadArtists.await() - playlists.value = loadPlaylists.await() - genres.value = loadGenres.await() - home.value = loadHome.await() + private fun fetchAlbums() { + viewModelScope.launch(IO) { + albums.postValue(repository.fetchAlbums()) + } } - private val loadHome: Deferred> - get() = viewModelScope.async { realRepository.homeSections() } - - private val loadSongs: Deferred> - get() = viewModelScope.async(IO) { realRepository.allSongs() } - - private val loadAlbums: Deferred> - get() = viewModelScope.async(IO) { - realRepository.allAlbums() + private fun fetchArtists() { + viewModelScope.launch(IO) { + artists.postValue(repository.fetchArtists()) } + } - private val loadArtists: Deferred> - get() = viewModelScope.async(IO) { - realRepository.albumArtists() + private fun fetchPlaylists() { + viewModelScope.launch(IO) { + playlists.postValue(repository.fetchPlaylistWithSongs()) } + } - private val loadPlaylists: Deferred> - get() = viewModelScope.async(IO) { - realRepository.allPlaylists() + private fun fetchLegacyPlaylist() { + viewModelScope.launch(IO) { + legacyPlaylists.postValue(repository.fetchLegacyPlaylist()) } + } - private val loadGenres: Deferred> - get() = viewModelScope.async(IO) { - realRepository.allGenres() + private fun fetchGenres() { + viewModelScope.launch(IO) { + genres.postValue(repository.fetchGenres()) } + } + private fun fetchHomeSections() { + viewModelScope.launch(IO) { + home.postValue(repository.homeSections()) + } + } fun forceReload(reloadType: ReloadType) = viewModelScope.launch { when (reloadType) { - Songs -> songs.value = loadSongs.await() - Albums -> albums.value = loadAlbums.await() - Artists -> artists.value = loadArtists.await() - HomeSections -> songs.value = loadSongs.await() + Songs -> fetchSongs() + Albums -> fetchAlbums() + Artists -> fetchArtists() + HomeSections -> fetchHomeSections() + Playlists -> fetchPlaylists() + Genres -> fetchGenres() } } @@ -89,11 +135,10 @@ class LibraryViewModel( } override fun onMediaStoreChanged() { - loadLibraryContent() println("onMediaStoreChanged") + loadLibraryContent() } - override fun onServiceConnected() { println("onServiceConnected") } @@ -108,6 +153,7 @@ class LibraryViewModel( override fun onPlayingMetaChanged() { println("onPlayingMetaChanged") + } override fun onPlayStateChanged() { @@ -122,11 +168,76 @@ class LibraryViewModel( println("onShuffleModeChanged") } + fun shuffleSongs() = viewModelScope.launch(IO) { + val songs = repository.allSongs() + MusicPlayerRemote.openAndShuffleQueue( + songs, + true + ) + } + + fun renameRoomPlaylist(playListId: Int, name: String) = viewModelScope.launch(IO) { + repository.renameRoomPlaylist(playListId, name) + } + + fun deleteSongsInPlaylist(songs: List) = viewModelScope.launch(IO) { + repository.deleteSongsInPlaylist(songs) + } + + fun deleteSongsFromPlaylist(playlists: List) = viewModelScope.launch(IO) { + repository.deletePlaylistSongs(playlists) + } + + fun deleteRoomPlaylist(playlists: List) = viewModelScope.launch(IO) { + repository.deleteRoomPlaylist(playlists) + } + + suspend fun albumById(id: Int) = repository.albumById(id) + suspend fun artistById(id: Int) = repository.artistById(id) + suspend fun favoritePlaylist() = repository.favoritePlaylist() + suspend fun isFavoriteSong(song: SongEntity) = repository.isFavoriteSong(song) + suspend fun insertSongs(songs: List) = repository.insertSongs(songs) + suspend fun removeSongFromPlaylist(songEntity: SongEntity) = + repository.removeSongFromPlaylist(songEntity) + + suspend fun checkPlaylistExists(playlistName: String): List = + repository.checkPlaylistExists(playlistName) + + suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = + repository.createPlaylist(playlistEntity) + + fun importPlaylists() = viewModelScope.launch(IO) { + val playlists = repository.fetchLegacyPlaylist() + playlists.forEach { playlist -> + val playlistEntity = repository.checkPlaylistExists(playlist.name).firstOrNull() + if (playlistEntity != null) { + val songEntities = playlist.getSongs().map { + it.toSongEntity(playlistEntity.playListId) + } + repository.insertSongs(songEntities) + } else { + val playListId = createPlaylist(PlaylistEntity(playlist.name)) + val songEntities = playlist.getSongs().map { + it.toSongEntity(playListId.toInt()) + } + repository.insertSongs(songEntities) + } + forceReload(Playlists) + } + } + + fun deleteTracks(songs: List) = viewModelScope.launch(IO) { + repository.deleteSongs(songs) + fetchPlaylists() + loadLibraryContent() + } } enum class ReloadType { Songs, Albums, Artists, - HomeSections + HomeSections, + Playlists, + Genres, } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt index 9f05dc43..9a16d656 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/MiniPlayerFragment.kt @@ -11,18 +11,14 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.animation.DecelerateInterpolator -import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.extensions.show -import code.name.monkey.retromusic.extensions.textColorPrimary -import code.name.monkey.retromusic.extensions.textColorSecondary +import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil -import code.name.monkey.retromusic.util.ViewUtil import kotlinx.android.synthetic.main.fragment_mini_player.* import kotlin.math.abs @@ -67,7 +63,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p private fun setUpMiniPlayer() { setUpPlayPauseButton() - ViewUtil.setProgressDrawable(progressBar, ThemeStore.accentColor(requireContext())) + progressBar.accentColor() } private fun setUpPlayPauseButton() { @@ -129,6 +125,10 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p } } + fun updateProgressBar(paletteColor: Int) { + progressBar.applyColor(paletteColor) + } + class FlingPlayBackController(context: Context) : View.OnTouchListener { private var flingPlayBackController: GestureDetector diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt index dacaff2d..19e08522 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/NowPlayingScreen.kt @@ -24,7 +24,6 @@ enum class NowPlayingScreen constructor( Gradient(R.string.gradient, R.drawable.np_gradient, 17), Material(R.string.material, R.drawable.np_material, 11), Normal(R.string.normal, R.drawable.np_normal, 0), - //Peak(R.string.peak, R.drawable.np_peak, 14), Plain(R.string.plain, R.drawable.np_plain, 3), Simple(R.string.simple, R.drawable.np_simple, 8), diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt index 9bc60bd5..bade59ff 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsFragment.kt @@ -6,7 +6,9 @@ import android.os.Bundle import android.view.* import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf +import androidx.core.text.HtmlCompat import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -25,16 +27,20 @@ import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.extensions.applyColor +import code.name.monkey.retromusic.extensions.applyOutlineColor import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.RetroMusicColoredTarget +import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmAlbum +import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil @@ -42,6 +48,10 @@ import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.fragment_album_content.* import kotlinx.android.synthetic.main.fragment_album_details.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.util.* @@ -66,23 +76,13 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det mainActivity.hideBottomBarVisibility(false) mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) - - toolbar.title = null - + toolbar.title = " " postponeEnterTransition() detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { - showAlbum(it) startPostponedEnterTransition() + showAlbum(it) }) - detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer { - loadArtistImage(it) - }) - detailsViewModel.getMoreAlbums().observe(viewLifecycleOwner, Observer { - moreAlbums(it) - }) - detailsViewModel.getAlbumInfo().observe(viewLifecycleOwner, Observer { - aboutAlbum(it) - }) + setupRecyclerView() artistImage.setOnClickListener { requireActivity().findNavController(R.id.fragment_container) @@ -140,14 +140,12 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det this.album = album albumTitle.text = album.title - val songText = - resources.getQuantityString( - R.plurals.albumSongs, - album.songCount, - album.songCount - ) + val songText = resources.getQuantityString( + R.plurals.albumSongs, + album.songCount, + album.songCount + ) songTitle.text = songText - if (MusicUtil.getYearString(album.year) == "-") { albumText.text = String.format( "%s • %s", @@ -162,10 +160,25 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) ) } - loadAlbumCover() + loadAlbumCover(album) simpleSongAdapter.swapDataSet(album.songs) - detailsViewModel.loadArtist(album.artistId) - detailsViewModel.loadAlbumInfo(album) + detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer { + loadArtistImage(it) + }) + + detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result -> + when (result) { + is Result.Loading -> { + println("Loading") + } + is Result.Error -> { + println("Error") + } + is Result.Success -> { + aboutAlbum(result.data) + } + } + }) } private fun moreAlbums(albums: List) { @@ -191,7 +204,10 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det aboutAlbumTitle.show() aboutAlbumTitle.text = String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) - aboutAlbumText.text = lastFmAlbum.album.wiki.content + aboutAlbumText.text = HtmlCompat.fromHtml( + lastFmAlbum.album.wiki.content, + HtmlCompat.FROM_HTML_MODE_LEGACY + ) } if (lastFmAlbum.album.listeners.isNotEmpty()) { listeners.show() @@ -206,7 +222,11 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } private fun loadArtistImage(artist: Artist) { + detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, Observer { + moreAlbums(it) + }) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) + .forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) .generatePalette(requireContext()) .build() .dontAnimate() @@ -217,24 +237,21 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det }) } - private fun loadAlbumCover() { + private fun loadAlbumCover(album: Album) { AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong()) - .checkIgnoreMediaStore(requireContext()) - .ignoreMediaStore(PreferenceUtil.isIgnoreMediaStoreArtwork) + .checkIgnoreMediaStore() .generatePalette(requireContext()) .build() - .dontAnimate() - .dontTransform() - .into(object : RetroMusicColoredTarget(image) { - override fun onColorReady(colors: MediaNotificationProcessor) { - setColors(colors) + .into(object : SingleColorTarget(image) { + override fun onColorReady(color: Int) { + setColors(color) } }) } - private fun setColors(color: MediaNotificationProcessor) { - shuffleAction.applyColor(color.backgroundColor) - playAction.applyColor(color.backgroundColor) + private fun setColors(color: Int) { + shuffleAction.applyColor(color) + playAction.applyOutlineColor(color) } override fun onAlbumClick(albumId: Int, view: View) { @@ -275,7 +292,13 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(songs).show(childFragmentManager, "ADD_PLAYLIST") + lifecycleScope.launch(Dispatchers.IO) { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, songs) + .show(childFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_delete_from_device -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt index 93b637e7..3311046e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt @@ -1,64 +1,44 @@ package code.name.monkey.retromusic.fragments.albums import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.liveData import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.repository.RealRepository -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers.IO class AlbumDetailsViewModel( private val realRepository: RealRepository, private val albumId: Int ) : ViewModel(), MusicServiceEventListener { - private val _album = MutableLiveData() - private val _artist = MutableLiveData() - private val _lastFmAlbum = MutableLiveData() - private val _moreAlbums = MutableLiveData>() - - fun getAlbum(): LiveData = _album - fun getArtist(): LiveData = _artist - fun getAlbumInfo(): LiveData = _lastFmAlbum - fun getMoreAlbums(): LiveData> = _moreAlbums; - - init { - loadAlbumDetails() + fun getAlbum(): LiveData = liveData(IO) { + val album = realRepository.albumByIdAsync(albumId) + emit(album) } - private fun loadAlbumDetails() = viewModelScope.launch { - val album = loadAlbumAsync.await() ?: throw NullPointerException("Album couldn't found") - _album.postValue(album) - } - - fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) { - val lastFmAlbum = realRepository.albumInfo(album.artistName ?: "-", album.title ?: "-") - _lastFmAlbum.postValue(lastFmAlbum) - } - - fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) { + fun getArtist(artistId: Int): LiveData = liveData(IO) { val artist = realRepository.artistById(artistId) - _artist.postValue(artist) - - artist.albums?.filter { item -> item.id != albumId }?.let { albums -> - if (albums.isNotEmpty()) _moreAlbums.postValue(albums) - } + emit(artist) } - private val loadAlbumAsync: Deferred - get() = viewModelScope.async(Dispatchers.IO) { - realRepository.albumById(albumId) + fun getAlbumInfo(album: Album): LiveData> = liveData { + emit(Result.Loading) + emit( realRepository.albumInfo(album.artistName ?: "-", album.title ?: "-")) + } + + fun getMoreAlbums(artist: Artist): LiveData> = liveData(IO) { + artist.albums?.filter { item -> item.id != albumId }?.let { albums -> + if (albums.isNotEmpty()) emit(albums) } + } override fun onMediaStoreChanged() { - loadAlbumDetails() + } override fun onServiceConnected() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt index 47c060da..9f7b7ce0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumsFragment.kt @@ -1,26 +1,29 @@ package code.name.monkey.retromusic.fragments.albums import android.os.Bundle -import android.view.View +import android.view.* import androidx.core.os.bundleOf import androidx.lifecycle.Observer -import androidx.navigation.findNavController import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.recyclerview.widget.GridLayoutManager import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.album.AlbumAdapter +import code.name.monkey.retromusic.extensions.findActivityNavController import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment +import code.name.monkey.retromusic.helper.SortOrder +import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.util.RetroUtil -class AlbumsFragment : - AbsRecyclerViewCustomGridSizeFragment(), + +class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), AlbumClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.albumsLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getAlbums().observe(viewLifecycleOwner, Observer { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -95,8 +98,7 @@ class AlbumsFragment : } override fun onAlbumClick(albumId: Int, view: View) { - val controller = requireActivity().findNavController(R.id.fragment_container) - controller.navigate( + findActivityNavController(R.id.fragment_container).navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, @@ -105,6 +107,181 @@ class AlbumsFragment : ) ) } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size) + if (RetroUtil.isLandscape()) { + gridSizeItem.setTitle(R.string.action_grid_size_land) + } + setUpGridSizeMenu(gridSizeItem.subMenu) + val layoutItem = menu.findItem(R.id.action_layout_type) + setupLayoutMenu(layoutItem.subMenu) + setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) + super.onCreateOptionsMenu(menu, inflater) + } + + private fun setUpSortOrderMenu( + sortOrderMenu: SubMenu + ) { + val currentSortOrder: String? = getSortOrder() + sortOrderMenu.clear() + sortOrderMenu.add( + 0, + R.id.action_album_sort_order_asc, + 0, + R.string.sort_order_a_z + ).isChecked = + currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) + sortOrderMenu.add( + 0, + R.id.action_album_sort_order_desc, + 1, + R.string.sort_order_z_a + ).isChecked = + currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A) + sortOrderMenu.add( + 0, + R.id.action_album_sort_order_artist, + 2, + R.string.sort_order_artist + ).isChecked = + currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST) + sortOrderMenu.add( + 0, + R.id.action_album_sort_order_year, + 3, + R.string.sort_order_year + ).isChecked = + currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_YEAR) + + sortOrderMenu.setGroupCheckable(0, true, true) + } + + private fun setupLayoutMenu( + subMenu: SubMenu + ) { + when (itemLayoutRes()) { + R.layout.item_card -> subMenu.findItem(R.id.action_layout_card).isChecked = true + R.layout.item_grid -> subMenu.findItem(R.id.action_layout_normal).isChecked = true + R.layout.item_card_color -> + subMenu.findItem(R.id.action_layout_colored_card).isChecked = true + R.layout.item_grid_circle -> + subMenu.findItem(R.id.action_layout_circular).isChecked = true + R.layout.image -> subMenu.findItem(R.id.action_layout_image).isChecked = true + R.layout.item_image_gradient -> + subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true + } + } + + private fun setUpGridSizeMenu( + gridSizeMenu: SubMenu + ) { + when (getGridSize()) { + 1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked = + true + 2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true + 3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true + 4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true + 5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true + 6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true + 7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true + 8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true + } + val gridSize: Int = maxGridSize + if (gridSize < 8) { + gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false + } + if (gridSize < 7) { + gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false + } + if (gridSize < 6) { + gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false + } + if (gridSize < 5) { + gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false + } + if (gridSize < 4) { + gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false + } + if (gridSize < 3) { + gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (handleGridSizeMenuItem(item)) { + return true + } + if (handleLayoutResType(item)) { + return true + } + if (handleSortOrderMenuItem(item)) { + return true + } + return super.onOptionsItemSelected(item) + } + + private fun handleSortOrderMenuItem( + item: MenuItem + ): Boolean { + var sortOrder: String? = null + + when (item.itemId) { + R.id.action_album_sort_order_asc -> sortOrder = AlbumSortOrder.ALBUM_A_Z + R.id.action_album_sort_order_desc -> sortOrder = AlbumSortOrder.ALBUM_Z_A + R.id.action_album_sort_order_artist -> sortOrder = AlbumSortOrder.ALBUM_ARTIST + R.id.action_album_sort_order_year -> sortOrder = AlbumSortOrder.ALBUM_YEAR + } + if (sortOrder != null) { + item.isChecked = true + setAndSaveSortOrder(sortOrder) + return true + } + return false + } + + private fun handleLayoutResType( + item: MenuItem + ): Boolean { + var layoutRes = -1 + when (item.itemId) { + R.id.action_layout_normal -> layoutRes = R.layout.item_grid + R.id.action_layout_card -> layoutRes = R.layout.item_card + R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color + R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle + R.id.action_layout_image -> layoutRes = R.layout.image + R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + } + if (layoutRes != -1) { + item.isChecked = true + setAndSaveLayoutRes(layoutRes) + return true + } + return false + } + + private fun handleGridSizeMenuItem( + item: MenuItem + ): Boolean { + var gridSize = 0 + when (item.itemId) { + R.id.action_grid_size_1 -> gridSize = 1 + R.id.action_grid_size_2 -> gridSize = 2 + R.id.action_grid_size_3 -> gridSize = 3 + R.id.action_grid_size_4 -> gridSize = 4 + R.id.action_grid_size_5 -> gridSize = 5 + R.id.action_grid_size_6 -> gridSize = 6 + R.id.action_grid_size_7 -> gridSize = 7 + R.id.action_grid_size_8 -> gridSize = 8 + } + if (gridSize > 0) { + item.isChecked = true + setAndSaveGridSize(gridSize) + return true + } + return false + } + } interface AlbumClickListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt index 8fc574eb..877def06 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt @@ -10,33 +10,43 @@ import android.view.View import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.extensions.applyColor +import code.name.monkey.retromusic.extensions.applyOutlineColor import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.fragments.albums.AlbumClickListener import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.glide.ArtistGlideRequest -import code.name.monkey.retromusic.glide.RetroMusicColoredTarget +import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Artist +import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmArtist +import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.CustomArtistImageUtil import code.name.monkey.retromusic.util.MusicUtil +import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil -import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.fragment_artist_content.* import kotlinx.android.synthetic.main.fragment_artist_details.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.util.* @@ -66,13 +76,10 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d setupRecyclerView() postponeEnterTransition() detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer { - showArtist(it) startPostponedEnterTransition() }) - detailsViewModel.getArtistInfo().observe(viewLifecycleOwner, Observer { - artistInfo(it) - }) + playAction.apply { setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } @@ -133,6 +140,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d albumTitle.text = albumText songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber }) artist.albums?.let { albumAdapter.swapDataSet(it) } + } private fun loadBiography( @@ -141,7 +149,14 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d ) { biography = null this.lang = lang - detailsViewModel.loadBiography(name, lang, null) + detailsViewModel.getArtistInfo(name, lang, null) + .observe(viewLifecycleOwner, Observer { result -> + when (result) { + is Result.Loading -> println("Loading") + is Result.Error -> println("Error") + is Result.Success -> artistInfo(result.data) + } + }) } private fun artistInfo(lastFmArtist: LastFmArtist?) { @@ -175,23 +190,26 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d private fun loadArtistImage(artist: Artist) { ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) .generatePalette(requireContext()).build() - .dontAnimate().into(object : RetroMusicColoredTarget(image) { - override fun onColorReady(colors: MediaNotificationProcessor) { - startPostponedEnterTransition() - setColors(colors) + .dontAnimate() + .into(object : SingleColorTarget(image) { + override fun onColorReady(color: Int) { + setColors(color) } }) } - private fun setColors(color: MediaNotificationProcessor) { - shuffleAction.applyColor(color.backgroundColor) - playAction.applyColor(color.backgroundColor) + private fun setColors(color: Int) { + val finalColor = if (PreferenceUtil.isAdaptiveColor) color + else ThemeStore.accentColor(requireContext()) + shuffleAction.applyColor(finalColor) + playAction.applyOutlineColor(finalColor) } + override fun onAlbumClick(albumId: Int, view: View) { findNavController().navigate( R.id.albumDetailsFragment, - bundleOf("extra_album_id" to albumId), + bundleOf(EXTRA_ALBUM_ID to albumId), null, FragmentNavigatorExtras( view to getString(R.string.transition_album_art) @@ -216,7 +234,13 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(songs).show(childFragmentManager, "ADD_PLAYLIST") + lifecycleScope.launch(Dispatchers.IO) { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, songs) + .show(childFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_set_artist_image -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt index aa6613a4..89039735 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt @@ -1,51 +1,37 @@ package code.name.monkey.retromusic.fragments.artists import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.liveData import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.model.Artist -import code.name.monkey.retromusic.repository.RealRepository +import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmArtist -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import code.name.monkey.retromusic.repository.RealRepository +import kotlinx.coroutines.Dispatchers.IO class ArtistDetailsViewModel( private val realRepository: RealRepository, private val artistId: Int ) : ViewModel(), MusicServiceEventListener { - private val loadArtistDetailsAsync: Deferred - get() = viewModelScope.async(Dispatchers.IO) { - realRepository.artistById(artistId) - } - - private val _artist = MutableLiveData() - private val _lastFmArtist = MutableLiveData() - - fun getArtist(): LiveData = _artist - fun getArtistInfo(): LiveData = _lastFmArtist - - init { - loadArtistDetails() + fun getArtist(): LiveData = liveData(IO) { + val artist = realRepository.artistById(artistId) + emit(artist) } - private fun loadArtistDetails() = viewModelScope.launch { - val artist = - loadArtistDetailsAsync.await() ?: throw NullPointerException("Album couldn't found") - _artist.postValue(artist) - } - - fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch { + fun getArtistInfo( + name: String, + lang: String?, + cache: String? + ): LiveData> = liveData(IO) { + emit(Result.Loading) val info = realRepository.artistInfo(name, lang, cache) - _lastFmArtist.postValue(info) + emit(info) } override fun onMediaStoreChanged() { - loadArtistDetails() + getArtist() } override fun onServiceConnected() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt index 6f2d91a1..b39909d3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistsFragment.kt @@ -1,7 +1,7 @@ package code.name.monkey.retromusic.fragments.artists import android.os.Bundle -import android.view.View +import android.view.* import android.widget.ImageView import androidx.core.os.bundleOf import androidx.lifecycle.Observer @@ -12,21 +12,17 @@ import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.extensions.findActivityNavController import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment -import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks +import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.util.RetroUtil -class ArtistsFragment : - AbsRecyclerViewCustomGridSizeFragment(), - MainActivityFragmentCallbacks, ArtistClickListener { - - override fun handleBackPress(): Boolean { - return false - } +class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment(), + ArtistClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.artistsLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -104,6 +100,161 @@ class ArtistsFragment : val controller = findActivityNavController(R.id.fragment_container) controller.navigate(R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId)) } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size) + if (RetroUtil.isLandscape()) { + gridSizeItem.setTitle(R.string.action_grid_size_land) + } + setUpGridSizeMenu(gridSizeItem.subMenu) + val layoutItem = menu.findItem(R.id.action_layout_type) + setupLayoutMenu(layoutItem.subMenu) + setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) + super.onCreateOptionsMenu(menu, inflater) + } + + private fun setUpSortOrderMenu( + sortOrderMenu: SubMenu + ) { + val currentSortOrder: String? = getSortOrder() + sortOrderMenu.clear() + sortOrderMenu.add( + 0, + R.id.action_artist_sort_order_asc, + 0, + R.string.sort_order_a_z + ).isChecked = currentSortOrder.equals(ArtistSortOrder.ARTIST_A_Z) + sortOrderMenu.add( + 0, + R.id.action_artist_sort_order_desc, + 1, + R.string.sort_order_z_a + ).isChecked = currentSortOrder.equals(ArtistSortOrder.ARTIST_Z_A) + sortOrderMenu.setGroupCheckable(0, true, true) + } + + private fun setupLayoutMenu( + subMenu: SubMenu + ) { + when (itemLayoutRes()) { + R.layout.item_card -> subMenu.findItem(R.id.action_layout_card).isChecked = true + R.layout.item_grid -> subMenu.findItem(R.id.action_layout_normal).isChecked = true + R.layout.item_card_color -> + subMenu.findItem(R.id.action_layout_colored_card).isChecked = true + R.layout.item_grid_circle -> + subMenu.findItem(R.id.action_layout_circular).isChecked = true + R.layout.image -> subMenu.findItem(R.id.action_layout_image).isChecked = true + R.layout.item_image_gradient -> + subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true + } + } + + private fun setUpGridSizeMenu( + gridSizeMenu: SubMenu + ) { + when (getGridSize()) { + 1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked = + true + 2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true + 3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true + 4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true + 5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true + 6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true + 7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true + 8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true + } + val gridSize: Int = maxGridSize + if (gridSize < 8) { + gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false + } + if (gridSize < 7) { + gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false + } + if (gridSize < 6) { + gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false + } + if (gridSize < 5) { + gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false + } + if (gridSize < 4) { + gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false + } + if (gridSize < 3) { + gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (handleGridSizeMenuItem(item)) { + return true + } + if (handleLayoutResType(item)) { + return true + } + if (handleSortOrderMenuItem(item)) { + return true + } + return super.onOptionsItemSelected(item) + } + + private fun handleSortOrderMenuItem( + item: MenuItem + ): Boolean { + var sortOrder: String? = null + + when (item.itemId) { + R.id.action_artist_sort_order_asc -> sortOrder = ArtistSortOrder.ARTIST_A_Z + R.id.action_artist_sort_order_desc -> sortOrder = ArtistSortOrder.ARTIST_Z_A + } + if (sortOrder != null) { + item.isChecked = true + setAndSaveSortOrder(sortOrder) + return true + } + return false + } + + private fun handleLayoutResType( + item: MenuItem + ): Boolean { + var layoutRes = -1 + when (item.itemId) { + R.id.action_layout_normal -> layoutRes = R.layout.item_grid + R.id.action_layout_card -> layoutRes = R.layout.item_card + R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color + R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle + R.id.action_layout_image -> layoutRes = R.layout.image + R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + } + if (layoutRes != -1) { + item.isChecked = true + setAndSaveLayoutRes(layoutRes) + return true + } + return false + } + + private fun handleGridSizeMenuItem( + item: MenuItem + ): Boolean { + var gridSize = 0 + when (item.itemId) { + R.id.action_grid_size_1 -> gridSize = 1 + R.id.action_grid_size_2 -> gridSize = 2 + R.id.action_grid_size_3 -> gridSize = 3 + R.id.action_grid_size_4 -> gridSize = 4 + R.id.action_grid_size_5 -> gridSize = 5 + R.id.action_grid_size_6 -> gridSize = 6 + R.id.action_grid_size_7 -> gridSize = 7 + R.id.action_grid_size_8 -> gridSize = 8 + } + if (gridSize > 0) { + item.isChecked = true + setAndSaveGridSize(gridSize) + return true + } + return false + } } interface ArtistClickListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsPlayerFragment.kt index f37423fe..59875b4f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsPlayerFragment.kt @@ -1,10 +1,9 @@ package code.name.monkey.retromusic.fragments.base -import android.annotation.SuppressLint import android.content.ContentUris import android.content.Intent +import android.graphics.drawable.Drawable import android.media.MediaMetadataRetriever -import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.provider.MediaStore @@ -15,30 +14,40 @@ import android.widget.Toast import androidx.annotation.LayoutRes import androidx.appcompat.widget.Toolbar import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import code.name.monkey.retromusic.EXTRA_ALBUM_ID import code.name.monkey.retromusic.EXTRA_ARTIST_ID import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.dialogs.* import code.name.monkey.retromusic.extensions.hide +import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.PaletteColorHolder import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.lyrics.Lyrics +import code.name.monkey.retromusic.repository.RealRepository +import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.* import kotlinx.android.synthetic.main.shadow_statusbar_toolbar.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.sharedViewModel import java.io.FileNotFoundException abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout), Toolbar.OnMenuItemClickListener, PaletteColorHolder, PlayerAlbumCoverFragment.Callbacks { - private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null - private var updateLyricsAsyncTask: AsyncTask<*, *, *>? = null private var playerAlbumCoverFragment: PlayerAlbumCoverFragment? = null protected val libraryViewModel by sharedViewModel() @@ -64,7 +73,13 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(song).show(childFragmentManager, "ADD_PLAYLIST") + lifecycleScope.launch(IO) { + val playlists = get().fetchPlaylists() + withContext(Main) { + AddToPlaylistDialog.create(playlists, song) + .show(childFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_clear_playing_queue -> { @@ -146,9 +161,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme return false } - protected open fun toggleFavorite(song: Song) { - MusicUtil.toggleFavorite(requireActivity(), song) - } abstract fun playerToolbar(): Toolbar? @@ -170,79 +182,70 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme updateLyrics() } - override fun onDestroyView() { - if (updateIsFavoriteTask != null && !updateIsFavoriteTask!!.isCancelled) { - updateIsFavoriteTask!!.cancel(true) - } - if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask!!.isCancelled) { - updateLyricsAsyncTask!!.cancel(true) - } - super.onDestroyView() - } - - @SuppressLint("StaticFieldLeak") - fun updateIsFavorite() { - if (updateIsFavoriteTask != null) { - updateIsFavoriteTask!!.cancel(false) - } - updateIsFavoriteTask = object : AsyncTask() { - override fun doInBackground(vararg params: Song): Boolean { - return MusicUtil.isFavorite(requireActivity(), params[0]) - } - - override fun onPostExecute(isFavorite: Boolean) { - val res = if (isFavorite) - R.drawable.ic_favorite - else - R.drawable.ic_favorite_border - - val drawable = - RetroUtil.getTintedVectorDrawable(requireContext(), res, toolbarIconColor()) - if (playerToolbar() != null && playerToolbar()!!.menu.findItem(R.id.action_toggle_favorite) != null) - playerToolbar()!!.menu.findItem(R.id.action_toggle_favorite).setIcon(drawable) - .title = - if (isFavorite) getString(R.string.action_remove_from_favorites) else getString( - R.string.action_add_to_favorites - ) - } - }.execute(MusicPlayerRemote.currentSong) - } - - @SuppressLint("StaticFieldLeak") - private fun updateLyrics() { - if (updateLyricsAsyncTask != null) updateLyricsAsyncTask!!.cancel(false) - - updateLyricsAsyncTask = object : AsyncTask() { - override fun onPreExecute() { - super.onPreExecute() - setLyrics(null) - } - - override fun doInBackground(vararg params: Song): Lyrics? { - try { - var data: String? = - LyricUtil.getStringFromFile(params[0].title, params[0].artistName) - return if (TextUtils.isEmpty(data)) { - data = MusicUtil.getLyrics(params[0]) - return if (TextUtils.isEmpty(data)) { - null - } else { - Lyrics.parse(params[0], data) - } - } else Lyrics.parse(params[0], data!!) - } catch (err: FileNotFoundException) { - return null + protected open fun toggleFavorite(song: Song) { + lifecycleScope.launch(IO) { + val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() + if (playlist != null) { + val songEntity = song.toSongEntity(playlist.playListId) + val isFavorite = libraryViewModel.isFavoriteSong(songEntity).isNotEmpty() + if (isFavorite) { + libraryViewModel.removeSongFromPlaylist(songEntity) + } else { + libraryViewModel.insertSongs(listOf(song.toSongEntity(playlist.playListId))) } } + libraryViewModel.forceReload(ReloadType.Playlists) + requireContext().sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED)) + } + } - override fun onPostExecute(l: Lyrics?) { - setLyrics(l) + fun updateIsFavorite() { + lifecycleScope.launch(IO) { + val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist() + if (playlist != null) { + val song: SongEntity = + MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId) + val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty() + withContext(Main) { + val icon = + if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border + val drawable: Drawable? = RetroUtil.getTintedVectorDrawable( + requireContext(), + icon, + toolbarIconColor() + ) + if (playerToolbar() != null) { + playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite) + ?.setIcon(drawable)?.title = + if (isFavorite) getString(R.string.action_remove_from_favorites) + else getString(R.string.action_add_to_favorites) + } + } } + } + } - override fun onCancelled(s: Lyrics?) { - onPostExecute(null) + private fun updateLyrics() { + setLyrics(null) + lifecycleScope.launch(IO) { + val song = MusicPlayerRemote.currentSong + val lyrics = try { + var data: String? = LyricUtil.getStringFromFile(song.title, song.artistName) + if (TextUtils.isEmpty(data)) { + data = MusicUtil.getLyrics(song) + if (TextUtils.isEmpty(data)) { + null + } else { + Lyrics.parse(song, data) + } + } else Lyrics.parse(song, data!!) + } catch (err: FileNotFoundException) { + null } - }.execute(MusicPlayerRemote.currentSong) + withContext(Main) { + setLyrics(lyrics) + } + } } open fun setLyrics(l: Lyrics?) { @@ -255,8 +258,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme ) { view.findViewById(R.id.status_bar).visibility = View.GONE } - playerAlbumCoverFragment = - childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment? + playerAlbumCoverFragment = whichFragment(R.id.playerAlbumCoverFragment) playerAlbumCoverFragment?.setCallbacks(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java index 65ad534a..4d5a0d09 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java @@ -290,7 +290,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements } } if (startIndex > -1) { - MusicPlayerRemote.INSTANCE.openQueue(songs, startIndex, true); + MusicPlayerRemote.openQueue(songs, startIndex, true); } else { final File finalFile = file1; Snackbar.make(coordinatorLayout, Html.fromHtml( diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt index 8924ffd9..fef2df72 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenreDetailsFragment.kt @@ -36,7 +36,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_ mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.setSupportActionBar(toolbar) mainActivity.hideBottomBarVisibility(false) - + progressIndicator.hide() setupRecyclerView() detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer { songs(it) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt index e4017e57..568da54a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/genres/GenresFragment.kt @@ -21,18 +21,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.GenreAdapter import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment -import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks -class GenresFragment : AbsRecyclerViewFragment(), - MainActivityFragmentCallbacks { - - override fun handleBackPress(): Boolean { - return false - } +class GenresFragment : AbsRecyclerViewFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.genresLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer { if (it.isNotEmpty()) adapter?.swapDataSet(it) else diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt index be901a82..460731af 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt @@ -16,50 +16,36 @@ package code.name.monkey.retromusic.fragments.home import android.app.ActivityOptions import android.os.Bundle -import android.util.DisplayMetrics +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem.SHOW_AS_ACTION_IF_ROOM import android.view.View import androidx.core.os.bundleOf import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope -import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import code.name.monkey.retromusic.EXTRA_PLAYLIST +import code.name.monkey.retromusic.HISTORY_PLAYLIST +import code.name.monkey.retromusic.LAST_ADDED_PLAYLIST import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.TOP_PLAYED_PLAYLIST import code.name.monkey.retromusic.adapter.HomeAdapter import code.name.monkey.retromusic.extensions.findActivityNavController import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest import code.name.monkey.retromusic.glide.UserProfileGlideRequest -import code.name.monkey.retromusic.helper.MusicPlayerRemote -import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist -import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist -import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist -import code.name.monkey.retromusic.repository.Repository import code.name.monkey.retromusic.util.NavigationUtil import code.name.monkey.retromusic.util.PreferenceUtil import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.abs_playlists.* import kotlinx.android.synthetic.main.fragment_banner_home.* import kotlinx.android.synthetic.main.home_content.* -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel class HomeFragment : AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home) { - private val repository by inject() private val libraryViewModel: LibraryViewModel by sharedViewModel() - private val displayMetrics: DisplayMetrics - get() { - val display = mainActivity.windowManager.defaultDisplay - val metrics = DisplayMetrics() - display.getMetrics(metrics) - return metrics - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setStatusBarColorAuto(view) @@ -74,31 +60,26 @@ class HomeFragment : lastAdded.setOnClickListener { findActivityNavController(R.id.fragment_container).navigate( - R.id.playlistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to LastAddedPlaylist()) + R.id.detailListFragment, + bundleOf("type" to LAST_ADDED_PLAYLIST) ) } topPlayed.setOnClickListener { findActivityNavController(R.id.fragment_container).navigate( - R.id.playlistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to TopTracksPlaylist()) + R.id.detailListFragment, + bundleOf("type" to TOP_PLAYED_PLAYLIST) ) } actionShuffle.setOnClickListener { - lifecycleScope.launch { - MusicPlayerRemote.openAndShuffleQueue( - repository.allSongs(), - true - ) - } + libraryViewModel.shuffleSongs() } history.setOnClickListener { - requireActivity().findNavController(R.id.fragment_container).navigate( - R.id.playlistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to HistoryPlaylist()) + findActivityNavController(R.id.fragment_container).navigate( + R.id.detailListFragment, + bundleOf("type" to HISTORY_PLAYLIST) ) } @@ -118,7 +99,7 @@ class HomeFragment : adapter = homeAdapter } - libraryViewModel.homeLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getHome().observe(viewLifecycleOwner, Observer { homeAdapter.swapData(it) }) @@ -138,6 +119,14 @@ class HomeFragment : ).build().into(userImage) } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + menu.removeItem(R.id.action_grid_size) + menu.removeItem(R.id.action_layout_type) + menu.removeItem(R.id.action_sort_order) + menu.findItem(R.id.action_settings).setShowAsAction(SHOW_AS_ACTION_IF_ROOM) + } + companion object { const val TAG: String = "BannerHomeFragment" diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt index 41d40f09..086e0a09 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/library/LibraryFragment.kt @@ -4,21 +4,26 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import androidx.core.text.HtmlCompat import androidx.navigation.fragment.findNavController import androidx.navigation.ui.NavigationUI +import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog +import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment -import com.google.android.material.appbar.AppBarLayout import kotlinx.android.synthetic.main.fragment_library.* +import java.lang.String class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) + retainInstance = true mainActivity.hideBottomBarVisibility(true) mainActivity.setSupportActionBar(toolbar) mainActivity.supportActionBar?.title = null @@ -30,6 +35,17 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { ) } setupNavigationController() + setupTitle() + } + + private fun setupTitle() { + val color = ThemeStore.accentColor(requireContext()) + val hexColor = String.format("#%06X", 0xFFFFFF and color) + val appName = HtmlCompat.fromHtml( + "Retro Music", + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + appNameText.text = appName } private fun setupNavigationController() { @@ -60,19 +76,15 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { null, navOptions ) + R.id.action_import_playlist -> ImportPlaylistDialog().show( + childFragmentManager, + "ImportPlaylist" + ) + R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show( + childFragmentManager, + "ShowCreatePlaylistDialog" + ) } return super.onOptionsItemSelected(item) } - - fun addOnAppBarOffsetChangedListener(changedListener: AppBarLayout.OnOffsetChangedListener) { - appBarLayout.addOnOffsetChangedListener(changedListener) - } - - fun removeOnAppBarOffsetChangedListener(changedListener: AppBarLayout.OnOffsetChangedListener) { - appBarLayout.removeOnOffsetChangedListener(changedListener) - } - - fun getTotalAppBarScrollingRange(): Int { - return 0 - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt index 323c89b4..c4748128 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/classic/ClassicPlayerFragment.kt @@ -20,7 +20,6 @@ import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior -import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show @@ -69,9 +68,7 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { - (requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() - .setAllowDragging(false) - + mainActivity.getBottomSheetBehavior().setAllowDragging(false) playerQueueSheet.setContentPadding( playerQueueSheet.contentPaddingLeft, (slideOffset * status_bar.height).toInt(), @@ -83,18 +80,17 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player } override fun onStateChanged(bottomSheet: View, newState: Int) { - val activity = requireActivity() as AbsSlidingMusicPanelActivity when (newState) { BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_DRAGGING -> { - activity.getBottomSheetBehavior().setAllowDragging(false) + mainActivity.getBottomSheetBehavior().setAllowDragging(false) } BottomSheetBehavior.STATE_COLLAPSED -> { resetToCurrentPosition() - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } else -> { - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } } } @@ -132,8 +128,7 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player playerQueueSheet.background = shapeDrawable playerQueueSheet.setOnTouchListener { _, _ -> - (requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() - .setAllowDragging(false) + mainActivity.getBottomSheetBehavior().setAllowDragging(false) getQueuePanel().setAllowDragging(true) return@setOnTouchListener false } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt index cf3bb2f6..fb31aa31 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/fit/FitPlaybackControlsFragment.kt @@ -3,9 +3,7 @@ package code.name.monkey.retromusic.fragments.player.fit import android.animation.ObjectAnimator import android.graphics.PorterDuff import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator @@ -26,7 +24,6 @@ import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil - import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_fit_playback_controls.* diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt index f51b1c75..0eacbb9a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt @@ -5,7 +5,6 @@ import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color import android.graphics.PorterDuff -import android.os.AsyncTask import android.os.Bundle import android.view.View import android.view.animation.LinearInterpolator @@ -18,10 +17,8 @@ import androidx.core.view.ViewCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import code.name.monkey.appthemehelper.util.ColorUtil -import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior -import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.ripAlpha @@ -62,14 +59,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null private var playingQueueAdapter: PlayingQueueAdapter? = null - private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null private lateinit var linearLayoutManager: LinearLayoutManager private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { - (requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() - .setAllowDragging(false) - + mainActivity.getBottomSheetBehavior().setAllowDragging(false) playerQueueSheet.setPadding( playerQueueSheet.paddingLeft, (slideOffset * status_bar.height).toInt(), @@ -79,18 +73,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } override fun onStateChanged(bottomSheet: View, newState: Int) { - val activity = requireActivity() as AbsSlidingMusicPanelActivity when (newState) { BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_DRAGGING -> { - activity.getBottomSheetBehavior().setAllowDragging(false) + mainActivity.getBottomSheetBehavior().setAllowDragging(false) } BottomSheetBehavior.STATE_COLLAPSED -> { resetToCurrentPosition() - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } else -> { - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } } } @@ -139,8 +132,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private fun setupSheet() { getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList) playerQueueSheet.setOnTouchListener { _, _ -> - (requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() - .setAllowDragging(false) + mainActivity.getBottomSheetBehavior().setAllowDragging(false) getQueuePanel().setAllowDragging(true) return@setOnTouchListener false } @@ -159,7 +151,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play recyclerViewDragDropManager?.cancelDrag() super.onPause() progressViewUpdateHelper.stop() - } override fun playerToolbar(): Toolbar? { @@ -224,9 +215,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun toggleFavorite(song: Song) { super.toggleFavorite(song) - MusicUtil.toggleFavorite(requireContext(), song) if (song.id == MusicPlayerRemote.currentSong.id) { - updateFavorite() + updateIsFavorite() } } @@ -274,6 +264,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun onQueueChanged() { super.onQueueChanged() updateLabel() + playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue) } private fun updateSong() { @@ -372,7 +363,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play } else { val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title nextSong.apply { - text = "Next: $title" + text = title show() } } @@ -478,36 +469,4 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } - - @SuppressLint("StaticFieldLeak") - private fun updateFavorite() { - if (updateIsFavoriteTask != null) { - updateIsFavoriteTask?.cancel(false) - } - updateIsFavoriteTask = - object : AsyncTask() { - override fun doInBackground(vararg params: Song): Boolean? { - val activity = activity - return if (activity != null) { - MusicUtil.isFavorite(requireActivity(), params[0]) - } else { - cancel(false) - null - } - } - - override fun onPostExecute(isFavorite: Boolean?) { - val activity = activity - if (activity != null) { - val res = if (isFavorite!!) - R.drawable.ic_favorite - else - R.drawable.ic_favorite_border - - val drawable = TintHelper.createTintedDrawable(activity, res, Color.WHITE) - songFavourite?.setImageDrawable(drawable) - } - } - }.execute(MusicPlayerRemote.currentSong) - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/ImportPlaylistFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/ImportPlaylistFragment.kt new file mode 100644 index 00000000..efe3c4e4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/ImportPlaylistFragment.kt @@ -0,0 +1,72 @@ +package code.name.monkey.retromusic.fragments.playlists + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import android.widget.Toast +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.adapter.playlist.LegacyPlaylistAdapter +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.fragments.ReloadType.Playlists +import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment +import code.name.monkey.retromusic.model.Playlist +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch + +class ImportPlaylistFragment : + AbsRecyclerViewFragment(), + LegacyPlaylistAdapter.PlaylistClickListener { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + libraryViewModel.getLegacyPlaylist().observe(viewLifecycleOwner, Observer { + if (it.isNotEmpty()) + adapter?.swapData(it) + else + adapter?.swapData(listOf()) + }) + } + + override fun createLayoutManager(): LinearLayoutManager { + return LinearLayoutManager(requireContext()) + } + + override fun createAdapter(): LegacyPlaylistAdapter { + return LegacyPlaylistAdapter( + requireActivity(), + ArrayList(), + R.layout.item_list_no_image, + this + ) + } + + override fun onPlaylistClick(playlist: Playlist) { + Toast.makeText(requireContext(), "Importing ${playlist.name}", Toast.LENGTH_LONG).show() + lifecycleScope.launch(IO) { + if (playlist.name.isNotEmpty()) { + if (libraryViewModel.checkPlaylistExists(playlist.name).isEmpty()) { + val playlistId: Long = + libraryViewModel.createPlaylist(PlaylistEntity(playlist.name)) + libraryViewModel.insertSongs(playlist.getSongs().map { + it.toSongEntity(playlistId.toInt()) + }) + libraryViewModel.forceReload(Playlists) + } else { + Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + menu.removeItem(R.id.action_grid_size) + menu.removeItem(R.id.action_layout_type) + menu.removeItem(R.id.action_sort_order) + super.onCreateOptionsMenu(menu, inflater) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt index 5c2d989b..8f78f518 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt @@ -5,18 +5,17 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import androidx.lifecycle.Observer import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.extensions.dipToPix import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper -import code.name.monkey.retromusic.model.AbsCustomPlaylist -import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.PlaylistsUtil import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator @@ -32,7 +31,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli parametersOf(arguments.extraPlaylist) } - private lateinit var playlist: Playlist + private lateinit var playlist: PlaylistWithSongs private lateinit var adapter: SongAdapter private var wrappedAdapter: RecyclerView.Adapter<*>? = null @@ -46,28 +45,23 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli mainActivity.hideBottomBarVisibility(false) playlist = arguments.extraPlaylist + toolbar.title = playlist.playlistEntity.playlistName setUpRecyclerView() - viewModel.getSongs().observe(viewLifecycleOwner, Observer { - songs(it) - }) - - viewModel.getPlaylist().observe(viewLifecycleOwner, Observer { - playlist = it - toolbar.title = it.name + viewModel.getSongs().observe(viewLifecycleOwner, { + songs(it.toSongs()) }) } private fun setUpRecyclerView() { recyclerView.layoutManager = LinearLayoutManager(requireContext()) - if (playlist is AbsCustomPlaylist) { - adapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null) - recyclerView.adapter = adapter - } else { - recyclerViewDragDropManager = RecyclerViewDragDropManager() - val animator = RefactoredDefaultItemAnimator() - adapter = OrderablePlaylistSongAdapter(requireActivity(), + recyclerViewDragDropManager = RecyclerViewDragDropManager() + val animator = RefactoredDefaultItemAnimator() + adapter = + OrderablePlaylistSongAdapter( + playlist.playlistEntity, + requireActivity(), ArrayList(), R.layout.item_list, null, @@ -75,7 +69,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli override fun onMoveItem(fromPosition: Int, toPosition: Int) { if (PlaylistsUtil.moveItem( requireContext(), - playlist.id, + playlist.playlistEntity.playListId, fromPosition, toPosition ) @@ -86,13 +80,13 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli } } }) - wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter) + wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter) - recyclerView.adapter = wrappedAdapter - recyclerView.itemAnimator = animator + recyclerView.adapter = wrappedAdapter + recyclerView.itemAnimator = animator + + recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - recyclerViewDragDropManager?.attachRecyclerView(recyclerView) - } adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() @@ -103,9 +97,9 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - val menuRes = if (playlist is AbsCustomPlaylist) + val menuRes =/* if (playlist is AbsCustomPlaylist) R.menu.menu_smart_playlist_detail - else R.menu.menu_playlist_detail + else*/ R.menu.menu_playlist_detail inflater.inflate(menuRes, menu) } @@ -129,7 +123,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli return String(Character.toChars(unicode)) } - public override fun onPause() { + override fun onPause() { if (recyclerViewDragDropManager != null) { recyclerViewDragDropManager!!.cancelDrag() } @@ -160,11 +154,11 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli } fun songs(songs: List) { + progressIndicator.hide() if (songs.isNotEmpty()) { adapter.swapDataSet(songs) } else { showEmptyView() } } - } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt index de61bdeb..ac498b4e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt @@ -3,42 +3,29 @@ package code.name.monkey.retromusic.fragments.playlists import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import code.name.monkey.retromusic.App +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.interfaces.MusicServiceEventListener -import code.name.monkey.retromusic.model.AbsCustomPlaylist -import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.RealRepository -import code.name.monkey.retromusic.util.PlaylistsUtil -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class PlaylistDetailsViewModel( private val realRepository: RealRepository, - private var playlist: Playlist + private var playlist: PlaylistWithSongs ) : ViewModel(), MusicServiceEventListener { + private val _playListSongs = MutableLiveData>() - private val _playlist = MutableLiveData().apply { + private val _playlist = MutableLiveData().apply { postValue(playlist) } - fun getPlaylist(): LiveData = _playlist + fun getPlaylist(): LiveData = _playlist - fun getSongs(): LiveData> = _playListSongs + fun getSongs(): LiveData> = realRepository.playlistSongs(playlist.playlistEntity) - init { - loadPlaylistSongs(playlist) - } - - private fun loadPlaylistSongs(playlist: Playlist) = viewModelScope.launch { - val songs = realRepository.getPlaylistSongs(playlist) - withContext(Main) { _playListSongs.postValue(songs) } - } override fun onMediaStoreChanged() { - if (playlist !is AbsCustomPlaylist) { + /*if (playlist !is AbsCustomPlaylist) { // Playlist deleted if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) { //TODO Finish the page @@ -54,7 +41,7 @@ class PlaylistDetailsViewModel( } } } - loadPlaylistSongs(playlist) + loadPlaylistSongs(playlist)*/ } override fun onServiceConnected() {} @@ -64,4 +51,4 @@ class PlaylistDetailsViewModel( override fun onPlayStateChanged() {} override fun onRepeatModeChanged() {} override fun onShuffleModeChanged() {} -} \ No newline at end of file +} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt index 22681715..95f849a2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt @@ -1,25 +1,23 @@ package code.name.monkey.retromusic.fragments.playlists import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import androidx.lifecycle.Observer -import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment -import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks +import kotlinx.android.synthetic.main.fragment_library.* -class PlaylistsFragment : - AbsRecyclerViewFragment(), - MainActivityFragmentCallbacks { - - override fun handleBackPress(): Boolean { - return false - } +class PlaylistsFragment : AbsRecyclerViewFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.playlisitsLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getPlaylists().observe(viewLifecycleOwner, Observer { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -30,8 +28,8 @@ class PlaylistsFragment : override val emptyMessage: Int get() = R.string.no_playlists - override fun createLayoutManager(): GridLayoutManager { - return GridLayoutManager(requireContext(), 1) + override fun createLayoutManager(): LinearLayoutManager { + return LinearLayoutManager(requireContext()) } override fun createAdapter(): PlaylistAdapter { @@ -43,9 +41,18 @@ class PlaylistsFragment : ) } - companion object { - fun newInstance(): PlaylistsFragment { - return PlaylistsFragment() - } + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + menu.removeItem(R.id.action_grid_size) + menu.removeItem(R.id.action_layout_type) + menu.removeItem(R.id.action_sort_order) + menu.add(0, R.id.action_add_to_playlist, 0, R.string.new_playlist_title) + menu.add(0, R.id.action_import_playlist, 0, R.string.import_playlist) + menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + super.onCreateOptionsMenu(menu, inflater) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt index f829f985..6376420c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/queue/PlayingQueueFragment.kt @@ -22,7 +22,6 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote -import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager @@ -33,13 +32,7 @@ import kotlinx.android.synthetic.main.activity_playing_queue.* /** * Created by hemanths on 2019-12-08. */ -class PlayingQueueFragment : - AbsRecyclerViewFragment(), - MainActivityFragmentCallbacks { - - override fun handleBackPress(): Boolean { - return false - } +class PlayingQueueFragment : AbsRecyclerViewFragment() { private lateinit var wrappedAdapter: RecyclerView.Adapter<*> private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchViewModel.kt index eecff933..50681023 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/search/SearchViewModel.kt @@ -6,9 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import code.name.monkey.retromusic.repository.RealRepository import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class SearchViewModel(private val realRepository: RealRepository) : ViewModel() { private val results = MutableLiveData>() @@ -17,6 +15,6 @@ class SearchViewModel(private val realRepository: RealRepository) : ViewModel() fun search(query: String?) = viewModelScope.launch(IO) { val result = realRepository.search(query) - withContext(Main) { results.postValue(result) } + results.value = result } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt index d0c15861..2b4e8a07 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/MainSettingsFragment.kt @@ -63,7 +63,7 @@ class MainSettingsFragment : Fragment(), View.OnClickListener { aboutSettings.setOnClickListener(this) buyProContainer.apply { - if (!App.isProVersion()) show() else hide() + if (App.isProVersion()) hide() else show() setOnClickListener { NavigationUtil.goToProVersion(requireContext()) } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt index 2338c6f7..c52d4adb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/settings/OtherSettingsFragment.kt @@ -19,12 +19,16 @@ import android.view.View import androidx.preference.Preference import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.fragments.LibraryViewModel +import code.name.monkey.retromusic.fragments.ReloadType.HomeSections +import org.koin.androidx.viewmodel.ext.android.sharedViewModel /** * @author Hemanth S (h4h13). */ class OtherSettingsFragment : AbsSettingsFragment() { + private val libraryViewModel by sharedViewModel() override fun invalidateSettings() { val languagePreference: ATEListPreference? = findPreference("language_name") languagePreference?.setOnPreferenceChangeListener { _, _ -> @@ -42,6 +46,7 @@ class OtherSettingsFragment : AbsSettingsFragment() { val preference: Preference? = findPreference("last_added_interval") preference?.setOnPreferenceChangeListener { lastAdded, newValue -> setSummary(lastAdded, newValue) + libraryViewModel.forceReload(HomeSections) true } val languagePreference: Preference? = findPreference("language_name") diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt index 3db01eb7..533aa0f7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/songs/SongsFragment.kt @@ -1,7 +1,7 @@ package code.name.monkey.retromusic.fragments.songs import android.os.Bundle -import android.view.View +import android.view.* import androidx.annotation.LayoutRes import androidx.lifecycle.Observer import androidx.recyclerview.widget.GridLayoutManager @@ -9,20 +9,17 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.fragments.ReloadType import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment -import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks +import code.name.monkey.retromusic.helper.SortOrder.SongSortOrder import code.name.monkey.retromusic.util.PreferenceUtil +import code.name.monkey.retromusic.util.RetroUtil -class SongsFragment : - AbsRecyclerViewCustomGridSizeFragment(), - MainActivityFragmentCallbacks { - override fun handleBackPress(): Boolean { - return false - } +class SongsFragment : AbsRecyclerViewCustomGridSizeFragment() { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.songsLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getSongs().observe(viewLifecycleOwner, Observer { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -42,7 +39,7 @@ class SongsFragment : return SongAdapter( requireActivity(), dataSet, - R.layout.item_list, + itemLayoutRes(), null ) } @@ -88,6 +85,213 @@ class SongsFragment : libraryViewModel.forceReload(ReloadType.Songs) } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size) + if (RetroUtil.isLandscape()) { + gridSizeItem.setTitle(R.string.action_grid_size_land) + } + setUpGridSizeMenu(gridSizeItem.subMenu) + val layoutItem = menu.findItem(R.id.action_layout_type) + setupLayoutMenu(layoutItem.subMenu) + setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu) + super.onCreateOptionsMenu(menu, inflater) + } + + private fun setUpSortOrderMenu( + sortOrderMenu: SubMenu + ) { + val currentSortOrder: String? = getSortOrder() + sortOrderMenu.clear() + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_asc, + 0, + R.string.sort_order_a_z + ).isChecked = + currentSortOrder == SongSortOrder.SONG_A_Z + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_desc, + 1, + R.string.sort_order_z_a + ).isChecked = + currentSortOrder == SongSortOrder.SONG_Z_A + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_artist, + 2, + R.string.sort_order_artist + ).isChecked = + currentSortOrder == SongSortOrder.SONG_ARTIST + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_album, + 3, + R.string.sort_order_album + ).isChecked = + currentSortOrder == SongSortOrder.SONG_ALBUM + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_year, + 4, + R.string.sort_order_year + ).isChecked = + currentSortOrder == SongSortOrder.SONG_YEAR + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_date, + 5, + R.string.sort_order_date + ).isChecked = + currentSortOrder == SongSortOrder.SONG_DATE + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_date_modified, + 6, + R.string.sort_order_date_modified + ).isChecked = + currentSortOrder == SongSortOrder.SONG_DATE_MODIFIED + sortOrderMenu.add( + 0, + R.id.action_song_sort_order_composer, + 7, + R.string.sort_order_composer + ).isChecked = + currentSortOrder == SongSortOrder.COMPOSER + + sortOrderMenu.setGroupCheckable(0, true, true) + } + + private fun setupLayoutMenu( + subMenu: SubMenu + ) { + when (itemLayoutRes()) { + R.layout.item_card -> subMenu.findItem(R.id.action_layout_card).isChecked = true + R.layout.item_grid -> subMenu.findItem(R.id.action_layout_normal).isChecked = true + R.layout.item_card_color -> + subMenu.findItem(R.id.action_layout_colored_card).isChecked = true + R.layout.item_grid_circle -> + subMenu.findItem(R.id.action_layout_circular).isChecked = true + R.layout.image -> subMenu.findItem(R.id.action_layout_image).isChecked = true + R.layout.item_image_gradient -> + subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true + } + } + + private fun setUpGridSizeMenu( + gridSizeMenu: SubMenu + ) { + when (getGridSize()) { + 1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked = + true + 2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true + 3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true + 4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true + 5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true + 6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true + 7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true + 8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true + } + val gridSize: Int = maxGridSize + if (gridSize < 8) { + gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false + } + if (gridSize < 7) { + gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false + } + if (gridSize < 6) { + gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false + } + if (gridSize < 5) { + gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false + } + if (gridSize < 4) { + gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false + } + if (gridSize < 3) { + gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (handleGridSizeMenuItem(item)) { + return true + } + if (handleLayoutResType(item)) { + return true + } + if (handleSortOrderMenuItem(item)) { + return true + } + return super.onOptionsItemSelected(item) + } + + private fun handleSortOrderMenuItem( + item: MenuItem + ): Boolean { + var sortOrder: String? = null + + when (item.itemId) { + R.id.action_song_sort_order_asc -> sortOrder = SongSortOrder.SONG_A_Z + R.id.action_song_sort_order_desc -> sortOrder = SongSortOrder.SONG_Z_A + R.id.action_song_sort_order_artist -> sortOrder = SongSortOrder.SONG_ARTIST + R.id.action_song_sort_order_album -> sortOrder = SongSortOrder.SONG_ALBUM + R.id.action_song_sort_order_year -> sortOrder = SongSortOrder.SONG_YEAR + R.id.action_song_sort_order_date -> sortOrder = SongSortOrder.SONG_DATE + R.id.action_song_sort_order_composer -> sortOrder = SongSortOrder.COMPOSER + R.id.action_song_sort_order_date_modified -> sortOrder = + SongSortOrder.SONG_DATE_MODIFIED + } + if (sortOrder != null) { + item.isChecked = true + setAndSaveSortOrder(sortOrder) + return true + } + return false + } + + private fun handleLayoutResType( + item: MenuItem + ): Boolean { + var layoutRes = -1 + when (item.itemId) { + R.id.action_layout_normal -> layoutRes = R.layout.item_grid + R.id.action_layout_card -> layoutRes = R.layout.item_card + R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color + R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle + R.id.action_layout_image -> layoutRes = R.layout.image + R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient + } + if (layoutRes != -1) { + item.isChecked = true + setAndSaveLayoutRes(layoutRes) + return true + } + return false + } + + private fun handleGridSizeMenuItem( + item: MenuItem + ): Boolean { + var gridSize = 0 + when (item.itemId) { + R.id.action_grid_size_1 -> gridSize = 1 + R.id.action_grid_size_2 -> gridSize = 2 + R.id.action_grid_size_3 -> gridSize = 3 + R.id.action_grid_size_4 -> gridSize = 4 + R.id.action_grid_size_5 -> gridSize = 5 + R.id.action_grid_size_6 -> gridSize = 6 + R.id.action_grid_size_7 -> gridSize = 7 + R.id.action_grid_size_8 -> gridSize = 8 + } + if (gridSize > 0) { + item.isChecked = true + setAndSaveGridSize(gridSize) + return true + } + return false + } + companion object { @JvmField var TAG: String = SongsFragment::class.java.simpleName diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java index fab61c61..82e65da2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/AlbumGlideRequest.java @@ -69,7 +69,7 @@ public class AlbumGlideRequest { } @NonNull - public Builder checkIgnoreMediaStore(@NonNull Context context) { + public Builder checkIgnoreMediaStore() { return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork()); } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt new file mode 100644 index 00000000..d4b2db1c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/SingleColorTarget.kt @@ -0,0 +1,39 @@ +package code.name.monkey.retromusic.glide + +import android.graphics.drawable.Drawable +import android.widget.ImageView +import code.name.monkey.appthemehelper.util.ATHUtil +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper +import code.name.monkey.retromusic.util.ColorUtil +import com.bumptech.glide.request.animation.GlideAnimation + + +abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) { + + protected val defaultFooterColor: Int + get() = ATHUtil.resolveColor(view.context, R.attr.colorControlNormal) + + abstract fun onColorReady(color: Int) + + override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { + super.onLoadFailed(e, errorDrawable) + onColorReady(defaultFooterColor) + } + + override fun onResourceReady( + resource: BitmapPaletteWrapper?, + glideAnimation: GlideAnimation? + ) { + super.onResourceReady(resource, glideAnimation) + resource?.let { + onColorReady( + ColorUtil.getColor( + it.palette, + ATHUtil.resolveColor(view.context, R.attr.colorPrimary) + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt index a6f70942..43d23120 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt @@ -15,8 +15,8 @@ package code.name.monkey.retromusic.glide.artistimage import android.content.Context -import code.name.monkey.retromusic.deezer.DeezerApiService -import code.name.monkey.retromusic.deezer.Data +import code.name.monkey.retromusic.model.Data +import code.name.monkey.retromusic.network.DeezerService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import com.bumptech.glide.Priority @@ -38,7 +38,7 @@ class ArtistImage(val artistName: String) class ArtistImageFetcher( private val context: Context, - private val deezerApiService: DeezerApiService, + private val deezerService: DeezerService, val model: ArtistImage, val urlLoader: ModelLoader, val width: Int, @@ -66,16 +66,16 @@ class ArtistImageFetcher( PreferenceUtil.isAllowedToDownloadMetadata() ) { val artists = model.artistName.split(",") - val response = deezerApiService.getArtistImage(artists[0]).execute() + val response = deezerService.getArtistImage(artists[0]).execute() if (!response.isSuccessful) { - throw IOException("Request failed with code: " + response.code()); + throw IOException("Request failed with code: " + response.code()) } if (isCancelled) return null return try { - val deezerResponse = response.body(); + val deezerResponse = response.body() val imageUrl = deezerResponse?.data?.get(0)?.let { getHighestQuality(it) } // Fragile way to detect a place holder image returned from Deezer: // ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg" @@ -106,7 +106,7 @@ class ArtistImageFetcher( class ArtistImageLoader( val context: Context, - private val deezerApiService: DeezerApiService, + private val deezerService: DeezerService, private val urlLoader: ModelLoader ) : StreamModelLoader { @@ -115,7 +115,7 @@ class ArtistImageLoader( width: Int, height: Int ): DataFetcher { - return ArtistImageFetcher(context, deezerApiService, model, urlLoader, width, height) + return ArtistImageFetcher(context, deezerService, model, urlLoader, width, height) } } @@ -123,7 +123,7 @@ class Factory( val context: Context ) : ModelLoaderFactory { - private var deezerApiService: DeezerApiService + private var deezerService: DeezerService private var okHttpFactory: OkHttpUrlLoader.Factory init { @@ -134,8 +134,8 @@ class Factory( .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .build() ) - deezerApiService = DeezerApiService.invoke( - DeezerApiService.createDefaultOkHttpClient(context) + deezerService = DeezerService.invoke( + DeezerService.createDefaultOkHttpClient(context) .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) @@ -156,7 +156,7 @@ class Factory( ): ModelLoader { return ArtistImageLoader( context!!, - deezerApiService, + deezerService, okHttpFactory.build(context, factories) ) } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt index 86bc78d6..fca8c228 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.kt @@ -21,7 +21,7 @@ import code.name.monkey.retromusic.R object HorizontalAdapterHelper { - const val LAYOUT_RES = R.layout.item_image + const val LAYOUT_RES = R.layout.item_album_card private const val TYPE_FIRST = 1 private const val TYPE_MIDDLE = 2 diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt index a56b1db2..35e87d80 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt @@ -13,8 +13,10 @@ */ package code.name.monkey.retromusic.helper -import android.content.Context +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.model.Playlist +import code.name.monkey.retromusic.model.Song import java.io.BufferedWriter import java.io.File import java.io.FileWriter @@ -24,14 +26,13 @@ object M3UWriter : M3UConstants { @JvmStatic @Throws(IOException::class) fun write( - context: Context, dir: File, playlist: Playlist ): File? { if (!dir.exists()) dir.mkdirs() val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION) val songs = playlist.getSongs() - if (songs.size > 0) { + if (songs.isNotEmpty()) { val bw = BufferedWriter(FileWriter(file)) bw.write(M3UConstants.HEADER) for (song in songs) { @@ -44,4 +45,25 @@ object M3UWriter : M3UConstants { } return file } + + @JvmStatic + @Throws(IOException::class) + fun writeIO(dir: File, playlistWithSongs: PlaylistWithSongs): File { + if (!dir.exists()) dir.mkdirs() + val fileName = "${playlistWithSongs.playlistEntity.playlistName}.${M3UConstants.EXTENSION}" + val file = File(dir, fileName) + val songs: List = playlistWithSongs.songs.toSongs() + if (songs.isNotEmpty()) { + val bufferedWriter = BufferedWriter(FileWriter(file)) + bufferedWriter.write(M3UConstants.HEADER) + songs.forEach { + bufferedWriter.newLine() + bufferedWriter.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title) + bufferedWriter.newLine() + bufferedWriter.write(it.data) + } + bufferedWriter.close() + } + return file + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt index b05d41f8..e4ad5a91 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.kt @@ -15,7 +15,6 @@ package code.name.monkey.retromusic.helper import android.app.SearchManager -import android.content.Context import android.os.Bundle import android.provider.MediaStore import code.name.monkey.retromusic.model.Song @@ -33,7 +32,7 @@ object SearchQueryHelper : KoinComponent { var songs = ArrayList() @JvmStatic - fun getSongs(context: Context, extras: Bundle): List { + fun getSongs(extras: Bundle): List { val query = extras.getString(SearchManager.QUERY, null) val artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null) val albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null) @@ -45,9 +44,9 @@ object SearchQueryHelper : KoinComponent { songRepository.makeSongCursor( ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf( - artistName.toLowerCase(), - albumName.toLowerCase(), - titleName.toLowerCase() + artistName.toLowerCase(Locale.getDefault()), + albumName.toLowerCase(Locale.getDefault()), + titleName.toLowerCase(Locale.getDefault()) ) ) ) @@ -59,7 +58,10 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ARTIST_SELECTION + AND + TITLE_SELECTION, - arrayOf(artistName.toLowerCase(), titleName.toLowerCase()) + arrayOf( + artistName.toLowerCase(Locale.getDefault()), + titleName.toLowerCase(Locale.getDefault()) + ) ) ) } @@ -70,7 +72,10 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ALBUM_SELECTION + AND + TITLE_SELECTION, - arrayOf(albumName.toLowerCase(), titleName.toLowerCase()) + arrayOf( + albumName.toLowerCase(Locale.getDefault()), + titleName.toLowerCase(Locale.getDefault()) + ) ) ) } @@ -81,7 +86,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ARTIST_SELECTION, - arrayOf(artistName.toLowerCase()) + arrayOf(artistName.toLowerCase(Locale.getDefault())) ) ) } @@ -92,7 +97,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ALBUM_SELECTION, - arrayOf(albumName.toLowerCase()) + arrayOf(albumName.toLowerCase(Locale.getDefault())) ) ) } @@ -103,7 +108,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( TITLE_SELECTION, - arrayOf(titleName.toLowerCase()) + arrayOf(titleName.toLowerCase(Locale.getDefault())) ) ) } @@ -113,7 +118,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ARTIST_SELECTION, - arrayOf(query.toLowerCase()) + arrayOf(query.toLowerCase(Locale.getDefault())) ) ) @@ -123,7 +128,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( ALBUM_SELECTION, - arrayOf(query.toLowerCase()) + arrayOf(query.toLowerCase(Locale.getDefault())) ) ) if (songs.isNotEmpty()) { @@ -132,7 +137,7 @@ object SearchQueryHelper : KoinComponent { songs = songRepository.songs( songRepository.makeSongCursor( TITLE_SELECTION, - arrayOf(query.toLowerCase()) + arrayOf(query.toLowerCase(Locale.getDefault())) ) ) return if (songs.isNotEmpty()) { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt index 08b9c3a2..8f40adb5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.kt @@ -22,7 +22,13 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.GenreRepository +import code.name.monkey.retromusic.repository.RealRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.koin.core.KoinComponent +import org.koin.core.get import org.koin.core.inject object GenreMenuHelper : KoinComponent { @@ -38,8 +44,13 @@ object GenreMenuHelper : KoinComponent { return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(getGenreSongs(genre)) - .show(activity.supportFragmentManager, "ADD_PLAYLIST") + CoroutineScope(Dispatchers.IO).launch { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, getGenreSongs(genre)) + .show(activity.supportFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_add_to_current_playing -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt index 37e5e3d1..971193e0 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.kt @@ -15,95 +15,70 @@ package code.name.monkey.retromusic.helper.menu -import android.app.Activity -import android.content.Context import android.view.MenuItem -import android.widget.Toast import androidx.fragment.app.FragmentActivity -import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog +import code.name.monkey.retromusic.dialogs.SavePlaylistDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote -import code.name.monkey.retromusic.misc.WeakContextAsyncTask -import code.name.monkey.retromusic.model.AbsCustomPlaylist -import code.name.monkey.retromusic.model.Playlist -import code.name.monkey.retromusic.model.Song -import code.name.monkey.retromusic.util.PlaylistsUtil +import code.name.monkey.retromusic.repository.RealRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.core.KoinComponent +import org.koin.core.get -object PlaylistMenuHelper { +object PlaylistMenuHelper : KoinComponent { fun handleMenuClick( activity: FragmentActivity, - playlist: Playlist, item: MenuItem + playlistWithSongs: PlaylistWithSongs, item: MenuItem ): Boolean { when (item.itemId) { R.id.action_play -> { - MusicPlayerRemote.openQueue(getPlaylistSongs(activity, playlist), 9, true) + MusicPlayerRemote.openQueue(playlistWithSongs.songs.toSongs(), 0, true) return true } R.id.action_play_next -> { - MusicPlayerRemote.playNext(getPlaylistSongs(activity, playlist)) + MusicPlayerRemote.playNext(playlistWithSongs.songs.toSongs()) return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(getPlaylistSongs(activity, playlist)) - .show(activity.supportFragmentManager, "ADD_PLAYLIST") + CoroutineScope(Dispatchers.IO).launch { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, playlistWithSongs.songs.toSongs()) + .show(activity.supportFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_add_to_current_playing -> { - MusicPlayerRemote.enqueue(getPlaylistSongs(activity, playlist)) + MusicPlayerRemote.enqueue(playlistWithSongs.songs.toSongs()) return true } R.id.action_rename_playlist -> { - RenamePlaylistDialog.create(playlist.id.toLong()) + RenamePlaylistDialog.create(playlistWithSongs.playlistEntity) .show(activity.supportFragmentManager, "RENAME_PLAYLIST") return true } R.id.action_delete_playlist -> { - DeletePlaylistDialog.create(playlist) + DeletePlaylistDialog.create(playlistWithSongs.playlistEntity) .show(activity.supportFragmentManager, "DELETE_PLAYLIST") return true } R.id.action_save_playlist -> { - SavePlaylistAsyncTask(activity).execute(playlist) + SavePlaylistDialog.create(playlistWithSongs) + .show(activity.supportFragmentManager, "SavePlaylist") return true } } return false } - - private fun getPlaylistSongs( - activity: Activity, - playlist: Playlist - ): List { - return if (playlist is AbsCustomPlaylist) { - playlist.songs() - } else { - playlist.getSongs() - } - } - - private class SavePlaylistAsyncTask internal constructor(context: Context) : - WeakContextAsyncTask(context) { - - override fun doInBackground(vararg params: Playlist): String { - return String.format( - App.getContext().getString( - R.string - .saved_playlist_to - ), PlaylistsUtil.savePlaylist(App.getContext(), params[0]) - ) - } - - override fun onPostExecute(string: String) { - super.onPostExecute(string) - val context = context - if (context != null) { - Toast.makeText(context, string, Toast.LENGTH_LONG).show() - } - } - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt index 0fb27c98..07e590a6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.kt @@ -32,10 +32,17 @@ import code.name.monkey.retromusic.dialogs.SongDetailDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.PaletteColorHolder import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.RingtoneManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.core.KoinComponent +import org.koin.core.get -object SongMenuHelper { +object SongMenuHelper : KoinComponent { const val MENU_RES = R.menu.menu_item_song fun handleMenuClick(activity: FragmentActivity, song: Song, menuItemId: Int): Boolean { @@ -63,8 +70,13 @@ object SongMenuHelper { return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(song) - .show(activity.supportFragmentManager, "ADD_PLAYLIST") + CoroutineScope(Dispatchers.IO).launch { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, song) + .show(activity.supportFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_play_next -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt index 871619e1..912e3c81 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.kt @@ -20,9 +20,15 @@ import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.repository.RealRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.core.KoinComponent +import org.koin.core.get - -object SongsMenuHelper { +object SongsMenuHelper : KoinComponent { fun handleMenuClick( activity: FragmentActivity, songs: List, @@ -38,8 +44,13 @@ object SongsMenuHelper { return true } R.id.action_add_to_playlist -> { - AddToPlaylistDialog.create(songs) - .show(activity.supportFragmentManager, "ADD_PLAYLIST") + CoroutineScope(Dispatchers.IO).launch { + val playlists = get().fetchPlaylists() + withContext(Dispatchers.Main) { + AddToPlaylistDialog.create(playlists, songs) + .show(activity.supportFragmentManager, "ADD_PLAYLIST") + } + } return true } R.id.action_delete_from_device -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java index c015b1ab..9405c68e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/Lrc.java @@ -11,20 +11,20 @@ public class Lrc { private long time; private String text; - public void setTime(long time) { - this.time = time; - } - - public void setText(String text) { - this.text = text; - } - public long getTime() { return time; } + public void setTime(long time) { + this.time = time; + } + public String getText() { return text; } + public void setText(String text) { + this.text = text; + } + } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java index 6b5991e1..66e20083 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcEntry.java @@ -23,6 +23,9 @@ import android.text.TextUtils; * 一行歌词实体 */ class LrcEntry implements Comparable { + public static final int GRAVITY_CENTER = 0; + public static final int GRAVITY_LEFT = 1; + public static final int GRAVITY_RIGHT = 2; private long time; private String text; private String secondText; @@ -31,9 +34,6 @@ class LrcEntry implements Comparable { * 歌词距离视图顶部的距离 */ private float offset = Float.MIN_VALUE; - public static final int GRAVITY_CENTER = 0; - public static final int GRAVITY_LEFT = 1; - public static final int GRAVITY_RIGHT = 2; LrcEntry(long time, String text) { this.time = time; diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java index 20f147a7..23d7bfc3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcUtils.java @@ -28,6 +28,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -77,7 +78,7 @@ class LrcUtils { List entryList = new ArrayList<>(); try { - BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), "utf-8")); + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { List list = parseLine(line); diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java index abd4d750..050bac4c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LrcView.java @@ -35,6 +35,8 @@ import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Scroller; +import androidx.core.content.res.ResourcesCompat; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -510,6 +512,7 @@ public class LrcView extends View { if (i > 0) { y += ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + mDividerHeight; } + mLrcPaint.setTypeface(ResourcesCompat.getFont(getContext(), R.font.sans)); if (i == mCurrentLine) { mLrcPaint.setTextSize(mCurrentTextSize); mLrcPaint.setColor(mCurrentTextColor); diff --git a/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java index f8689439..5b206ba2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java +++ b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java @@ -63,9 +63,8 @@ public class CategoryInfo implements Parcelable { Songs(R.id.action_song, R.string.songs, R.drawable.ic_audiotrack), Albums(R.id.action_album, R.string.albums, R.drawable.ic_album), Artists(R.id.action_artist, R.string.artists, R.drawable.ic_artist), - Playlists(R.id.action_playlist, R.string.playlists, R.drawable.ic_playlist_play), + Playlists(R.id.action_playlist, R.string.playlists, (R.drawable.ic_queue_music)), Genres(R.id.action_genre, R.string.genres, R.drawable.ic_guitar), - Queue(R.id.action_playing_queue, R.string.queue, R.drawable.ic_queue_music), Folder(R.id.action_folder, R.string.folders, R.drawable.ic_folder); public final int icon; diff --git a/app/src/main/java/code/name/monkey/retromusic/deezer/DeezerResponse.kt b/app/src/main/java/code/name/monkey/retromusic/model/DeezerResponse.kt similarity index 94% rename from app/src/main/java/code/name/monkey/retromusic/deezer/DeezerResponse.kt rename to app/src/main/java/code/name/monkey/retromusic/model/DeezerResponse.kt index b029ab53..929a9d13 100644 --- a/app/src/main/java/code/name/monkey/retromusic/deezer/DeezerResponse.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/DeezerResponse.kt @@ -1,4 +1,4 @@ -package code.name.monkey.retromusic.deezer +package code.name.monkey.retromusic.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Home.kt b/app/src/main/java/code/name/monkey/retromusic/model/Home.kt index e848d25c..2224417f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Home.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Home.kt @@ -14,10 +14,13 @@ package code.name.monkey.retromusic.model +import androidx.annotation.StringRes import code.name.monkey.retromusic.HomeSection class Home( val arrayList: List, @HomeSection - val homeSection: Int + val homeSection: Int, + @StringRes + val titleRes: Int ) \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Song.kt b/app/src/main/java/code/name/monkey/retromusic/model/Song.kt index 9b382295..0fe59d8c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/Song.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/Song.kt @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.model import android.os.Parcelable +import code.name.monkey.retromusic.db.HistoryEntity +import code.name.monkey.retromusic.db.SongEntity import kotlinx.android.parcel.Parcelize @Parcelize @@ -32,6 +34,43 @@ open class Song( val composer: String?, val albumArtist: String? ) : Parcelable { + fun toHistoryEntity(timePlayed: Long): HistoryEntity { + return HistoryEntity( + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist, + timePlayed + ) + } + + fun toSongEntity(playListId: Int): SongEntity { + return SongEntity( + playListId, + id, + title, + trackNumber, + year, + duration, + data, + dateModified, + albumId, + albumName, + artistId, + artistName, + composer, + albumArtist + ) + } companion object { diff --git a/app/src/main/java/code/name/monkey/retromusic/deezer/DeezerApiService.kt b/app/src/main/java/code/name/monkey/retromusic/network/DeezerService.kt similarity index 92% rename from app/src/main/java/code/name/monkey/retromusic/deezer/DeezerApiService.kt rename to app/src/main/java/code/name/monkey/retromusic/network/DeezerService.kt index fd107234..1ea0c16c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/deezer/DeezerApiService.kt +++ b/app/src/main/java/code/name/monkey/retromusic/network/DeezerService.kt @@ -1,6 +1,7 @@ -package code.name.monkey.retromusic.deezer +package code.name.monkey.retromusic.network import android.content.Context +import code.name.monkey.retromusic.model.DeezerResponse import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -16,7 +17,7 @@ import java.util.* private const val BASE_QUERY_ARTIST = "search/artist" private const val BASE_URL = "https://api.deezer.com/" -interface DeezerApiService { +interface DeezerService { @GET("$BASE_QUERY_ARTIST&limit=1") fun getArtistImage( @@ -26,7 +27,7 @@ interface DeezerApiService { companion object { operator fun invoke( client: okhttp3.Call.Factory - ): DeezerApiService { + ): DeezerService { return Retrofit.Builder() .baseUrl(BASE_URL) .callFactory(client) diff --git a/app/src/main/java/code/name/monkey/retromusic/network/LyricsService.kt b/app/src/main/java/code/name/monkey/retromusic/network/LyricsService.kt new file mode 100644 index 00000000..0a0e0490 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/network/LyricsService.kt @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.network + +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface LyricsRestService { + + @Headers("Cache-Control: public") + @GET("/lyrics") + suspend fun getLyrics(@Query("artist") artist: String, @Query("title") title: String): String +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/Result.kt b/app/src/main/java/code/name/monkey/retromusic/network/Result.kt similarity index 88% rename from app/src/main/java/code/name/monkey/retromusic/Result.kt rename to app/src/main/java/code/name/monkey/retromusic/network/Result.kt index 4c241a14..2d155522 100644 --- a/app/src/main/java/code/name/monkey/retromusic/Result.kt +++ b/app/src/main/java/code/name/monkey/retromusic/network/Result.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package code.name.monkey.retromusic +package code.name.monkey.retromusic.network /** * Generic class that holds the network state @@ -21,5 +21,5 @@ package code.name.monkey.retromusic sealed class Result { data class Success(val data: T) : Result() object Loading : Result() - object Error : Result() + data class Error(val error: Exception) : Result() } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt b/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt index afaaeb67..719d682f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt +++ b/app/src/main/java/code/name/monkey/retromusic/network/RetrofitClient.kt @@ -1,66 +1,19 @@ package code.name.monkey.retromusic.network +import android.content.Context import code.name.monkey.retromusic.App -import code.name.monkey.retromusic.Constants.BASE_URL -import com.google.gson.Gson +import code.name.monkey.retromusic.BuildConfig +import code.name.monkey.retromusic.network.conversion.LyricsConverterFactory +import com.google.gson.GsonBuilder import okhttp3.Cache -import okhttp3.ConnectionPool import okhttp3.Interceptor import okhttp3.OkHttpClient -import org.koin.dsl.module +import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.io.File import java.util.concurrent.TimeUnit -private const val TIMEOUT: Long = 700 - -val networkModule = module { - factory { - provideHttpClient(get(), get()) - } - factory { - provideCacheControlInterceptor() - } - factory { - provideDefaultCache() - } - factory { - provideLastFmService(get()) - } - single { - providerRetrofit(get()) - } -} - -fun provideLastFmService(retrofit: Retrofit): LastFMService = - retrofit.create(LastFMService::class.java) - -fun providerRetrofit(okHttpClient: OkHttpClient.Builder): Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .callFactory(okHttpClient.build()) - .addConverterFactory(GsonConverterFactory.create(Gson())) - .build() - -fun provideHttpClient( - cache: Cache, - interceptor: Interceptor -): OkHttpClient.Builder = OkHttpClient.Builder() - .connectionPool(ConnectionPool(0, 1, TimeUnit.NANOSECONDS)) - .retryOnConnectionFailure(true) - .connectTimeout(TIMEOUT, TimeUnit.MINUTES) - .writeTimeout(TIMEOUT, TimeUnit.MINUTES) - .readTimeout(TIMEOUT, TimeUnit.MINUTES) - .cache(cache) - .addInterceptor(interceptor) - - -fun provideCacheControlInterceptor(): Interceptor = Interceptor { chain: Interceptor.Chain -> - val modifiedRequest = chain.request().newBuilder() - .addHeader("Cache-Control", "max-age=31536000, max-stale=31536000") - .build() - chain.proceed(modifiedRequest) -} fun provideDefaultCache(): Cache? { val cacheDir = File(App.getContext().cacheDir.absolutePath, "/okhttp-lastfm/") @@ -69,3 +22,66 @@ fun provideDefaultCache(): Cache? { } return null } + +fun logInterceptor(): Interceptor { + val loggingInterceptor = HttpLoggingInterceptor() + if (BuildConfig.DEBUG) { + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + } else { + // disable retrofit log on release + loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE + } + return loggingInterceptor +} + +fun headerInterceptor(context: Context): Interceptor { + return Interceptor { + val original = it.request() + val request = original.newBuilder() + .header("User-Agent", context.packageName) + .addHeader("Content-Type", "application/json; charset=utf-8") + .method(original.method(), original.body()) + .build() + it.proceed(request) + } +} + +fun provideOkHttp(context: Context, cache: Cache): OkHttpClient { + return OkHttpClient.Builder() + .addNetworkInterceptor(logInterceptor()) + //.addInterceptor(headerInterceptor(context)) + .connectTimeout(1, TimeUnit.SECONDS) + .readTimeout(1, TimeUnit.SECONDS) + .cache(cache) + .build() +} + +fun provideLastFmRetrofit(client: OkHttpClient): Retrofit { + val gson = GsonBuilder() + .setLenient() + .create() + return Retrofit.Builder() + .baseUrl("https://ws.audioscrobbler.com/2.0/") + .addConverterFactory(GsonConverterFactory.create(gson)) + .callFactory { request -> client.newCall(request) } + .build() +} + +fun provideLastFmRest(retrofit: Retrofit): LastFMService { + return retrofit.create(LastFMService::class.java) +} + +fun provideDeezerRest(retrofit: Retrofit): DeezerService { + val newBuilder = retrofit.newBuilder() + .baseUrl("https://api.deezer.com/") + .build() + return newBuilder.create(DeezerService::class.java) +} + +fun provideLyrics(retrofit: Retrofit): LyricsRestService { + val newBuilder = retrofit.newBuilder() + .baseUrl("https://makeitpersonal.co") + .addConverterFactory(LyricsConverterFactory()) + .build() + return newBuilder.create(LyricsRestService::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt b/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt new file mode 100644 index 00000000..474e81c9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/network/conversion/LyricsConverterFactory.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Naman Dwivedi. + * + * 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.network.conversion + +import okhttp3.MediaType +import okhttp3.RequestBody +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Retrofit +import java.lang.reflect.Type + +class LyricsConverterFactory : Converter.Factory() { + + override fun responseBodyConverter( + type: Type?, + annotations: Array?, + retrofit: Retrofit? + ): Converter? { + return if (String::class.java == type) { + Converter { value -> value.string() } + } else null + } + + override fun requestBodyConverter( + type: Type?, + parameterAnnotations: Array?, + methodAnnotations: Array?, + retrofit: Retrofit? + ): Converter<*, RequestBody>? { + + return if (String::class.java == type) { + Converter { value -> RequestBody.create(MEDIA_TYPE, value) } + } else null + } + + companion object { + private val MEDIA_TYPE = MediaType.parse("text/plain") + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt index acf4111d..f74f61d6 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/AlbumCoverStylePreferenceDialog.kt @@ -106,7 +106,7 @@ class AlbumCoverStylePreferenceDialog : DialogFragment(), override fun onPageScrollStateChanged(state: Int) { } - private class AlbumCoverStyleAdapter internal constructor(private val context: Context) : + private class AlbumCoverStyleAdapter(private val context: Context) : PagerAdapter() { override fun instantiateItem(collection: ViewGroup, position: Int): Any { diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt b/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt index 2cdd09ce..7ff912e3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/LibraryPreference.kt @@ -72,9 +72,7 @@ class LibraryPreferenceDialog : DialogFragment() { categoryAdapter.categoryInfos = PreferenceUtil.defaultCategories } .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton( - android.R.string.ok - ) { _, _ -> updateCategories(categoryAdapter.categoryInfos) } + .setPositiveButton(android.R.string.ok) { _, _ -> updateCategories(categoryAdapter.categoryInfos) } .setView(view) .create() .colorButtons() diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java index 6201e5a0..4701c062 100644 --- a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java +++ b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java @@ -108,7 +108,7 @@ public class SongPlayCountStore extends SQLiteOpenHelper { */ @NonNull private static String getColumnNameForWeek(final int week) { - return SongPlayCountColumns.WEEK_PLAY_COUNT + String.valueOf(week); + return SongPlayCountColumns.WEEK_PLAY_COUNT + week; } /** diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/PlaylistSongsLoader.kt b/app/src/main/java/code/name/monkey/retromusic/repository/PlaylistSongsLoader.kt index 383785b8..96f86d37 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/PlaylistSongsLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/PlaylistSongsLoader.kt @@ -23,7 +23,6 @@ import code.name.monkey.retromusic.model.AbsCustomPlaylist import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.model.Song -import java.util.* /** * Created by hemanths on 16/08/17. @@ -43,8 +42,8 @@ object PlaylistSongsLoader { } @JvmStatic - fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList { - val songs = arrayListOf() + fun getPlaylistSongList(context: Context, playlistId: Int): List { + val songs = mutableListOf() val cursor = makePlaylistSongCursor( context, diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt index d035a72d..7ea0db79 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/Repository.kt @@ -15,10 +15,15 @@ package code.name.monkey.retromusic.repository import android.content.Context +import androidx.lifecycle.LiveData import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.db.* import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.smartplaylist.NotPlayedPlaylist import code.name.monkey.retromusic.network.LastFMService +import code.name.monkey.retromusic.network.LyricsRestService +import code.name.monkey.retromusic.network.Result +import code.name.monkey.retromusic.network.Result.* import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmArtist import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -28,71 +33,71 @@ import kotlinx.coroutines.flow.flow interface Repository { - suspend fun allAlbums(): List - - suspend fun albumById(albumId: Int): Album - - suspend fun allSongs(): List - - suspend fun allArtists(): List - - suspend fun albumArtists(): List - - suspend fun allPlaylists(): List - - suspend fun allGenres(): List - - suspend fun search(query: String?): MutableList - - suspend fun getPlaylistSongs(playlist: Playlist): List - - suspend fun getGenre(genreId: Int): List - - 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(): List - - suspend fun topArtists(): List - - suspend fun topAlbums(): List - - suspend fun recentAlbums(): List - - suspend fun recentArtistsHome(): Home - - suspend fun topArtistsHome(): Home - - suspend fun topAlbumsHome(): Home - - suspend fun recentAlbumsHome(): Home - - suspend fun favoritePlaylistHome(): Home - - suspend fun suggestionsHome(): Home - - suspend fun genresHome(): Home - - suspend fun playlists(): Home - - suspend fun homeSections(): List - - suspend fun homeSectionsFlow(): Flow>> - fun songsFlow(): Flow>> - fun albumsFlow(): Flow>> - fun artistsFlow(): Flow>> - fun playlistsFlow(): Flow>> - fun genresFlow(): Flow>> - + fun historySong(): List + fun favorites(): LiveData> + fun observableHistorySongs(): LiveData> + fun albumById(albumId: Int): Album + fun playlistSongs(playlistEntity: PlaylistEntity): LiveData> + suspend fun fetchAlbums(): List + suspend fun albumByIdAsync(albumId: Int): Album + suspend fun allSongs(): List + suspend fun fetchArtists(): List + suspend fun albumArtists(): List + suspend fun fetchLegacyPlaylist(): List + suspend fun fetchGenres(): List + suspend fun search(query: String?): MutableList + suspend fun getPlaylistSongs(playlist: Playlist): List + suspend fun getGenre(genreId: Int): List + suspend fun artistInfo(name: String, lang: String?, cache: String?): Result + suspend fun albumInfo(artist: String, album: String): Result + suspend fun artistById(artistId: Int): Artist + suspend fun recentArtists(): List + suspend fun topArtists(): List + suspend fun topAlbums(): List + suspend fun recentAlbums(): List + suspend fun recentArtistsHome(): Home + suspend fun topArtistsHome(): Home + suspend fun topAlbumsHome(): Home + suspend fun recentAlbumsHome(): Home + suspend fun favoritePlaylistHome(): Home + suspend fun suggestionsHome(): Home + suspend fun genresHome(): Home + suspend fun playlists(): Home + suspend fun homeSections(): List + suspend fun homeSectionsFlow(): Flow>> suspend fun playlist(playlistId: Int): Playlist + suspend fun fetchPlaylistWithSongs(): List + suspend fun playlistSongs(playlistWithSongs: PlaylistWithSongs): List + suspend fun insertSongs(songs: List) + suspend fun checkPlaylistExists(playlistName: String): List + suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long + suspend fun fetchPlaylists(): List + suspend fun deleteRoomPlaylist(playlists: List) + suspend fun renameRoomPlaylist(playlistId: Int, name: String) + suspend fun deleteSongsInPlaylist(songs: List) + suspend fun removeSongFromPlaylist(songEntity: SongEntity) + suspend fun deletePlaylistSongs(playlists: List) + suspend fun favoritePlaylist(): PlaylistEntity + suspend fun isFavoriteSong(songEntity: SongEntity): List + suspend fun addSongToHistory(currentSong: Song) + suspend fun songPresentInHistory(currentSong: Song): HistoryEntity? + suspend fun updateHistorySong(currentSong: Song) + suspend fun favoritePlaylistSongs(): List + suspend fun recentSongs(): List + suspend fun topPlayedSongs(): List + suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun checkSongExistInPlayCount(songId: Int): List + suspend fun playCountSongs(): List + suspend fun blackListPaths(): List + suspend fun lyrics(artist: String, title: String): Result + suspend fun deleteSongs(songs: List) } class RealRepository( @@ -105,14 +110,27 @@ class RealRepository( private val lastAddedRepository: LastAddedRepository, private val playlistRepository: PlaylistRepository, private val searchRepository: RealSearchRepository, - private val playedTracksRepository: TopPlayedRepository + private val topPlayedRepository: TopPlayedRepository, + private val roomRepository: RoomRepository, + private val lyricsRestService: LyricsRestService ) : Repository { - override suspend fun allAlbums(): List = albumRepository.albums() + override suspend fun lyrics(artist: String, title: String): Result = try { + Success(lyricsRestService.getLyrics(artist, title)) + } catch (e: Exception) { + println(e) + Error(e) + } - override suspend fun albumById(albumId: Int): Album = albumRepository.album(albumId) + override suspend fun deleteSongs(songs: List) = roomRepository.deleteSongs(songs) - override suspend fun allArtists(): List = artistRepository.artists() + override suspend fun fetchAlbums(): List = albumRepository.albums() + + override suspend fun albumByIdAsync(albumId: Int): Album = albumRepository.album(albumId) + + override fun albumById(albumId: Int): Album = albumRepository.album(albumId) + + override suspend fun fetchArtists(): List = artistRepository.artists() override suspend fun albumArtists(): List = artistRepository.albumArtists() @@ -122,47 +140,57 @@ class RealRepository( override suspend fun recentAlbums(): List = lastAddedRepository.recentAlbums() - override suspend fun topArtists(): List = playedTracksRepository.topArtists() + override suspend fun topArtists(): List = topPlayedRepository.topArtists() - override suspend fun topAlbums(): List = playedTracksRepository.topAlbums() + override suspend fun topAlbums(): List = topPlayedRepository.topAlbums() - override suspend fun allPlaylists(): List = playlistRepository.playlists() + override suspend fun fetchLegacyPlaylist(): List = playlistRepository.playlists() - override suspend fun allGenres(): List = genreRepository.genres() + override suspend fun fetchGenres(): List = genreRepository.genres() override suspend fun allSongs(): List = songRepository.songs() - override suspend fun search(query: String?): MutableList = searchRepository.searchAll(context, query) - override suspend fun getPlaylistSongs(playlist: Playlist): List { - return if (playlist is AbsCustomPlaylist) { + override suspend fun getPlaylistSongs(playlist: Playlist): List = + if (playlist is AbsCustomPlaylist) { playlist.songs() } else { PlaylistSongsLoader.getPlaylistSongList(context, playlist.id) } - } override suspend fun getGenre(genreId: Int): List = genreRepository.songs(genreId) - override suspend fun artistInfo( name: String, lang: String?, cache: String? - ): LastFmArtist = lastFMService.artistInfo(name, lang, cache) - + ): Result { + return try { + Success(lastFMService.artistInfo(name, lang, cache)) + } catch (e: Exception) { + println(e) + Error(e) + } + } override suspend fun albumInfo( artist: String, album: String - ): LastFmAlbum = lastFMService.albumInfo(artist, album) + ): Result { + return try { + val lastFmAlbum = lastFMService.albumInfo(artist, album) + Success(lastFmAlbum) + } catch (e: Exception) { + println(e) + Error(e) + } + } @ExperimentalCoroutinesApi override suspend fun homeSectionsFlow(): Flow>> { - val homes = MutableStateFlow>>(value = Result.Loading) - println("homeSections:Loading") + val homes = MutableStateFlow>>(value = Loading) val homeSections = mutableListOf() val sections = listOf( topArtistsHome(), @@ -180,132 +208,214 @@ class RealRepository( } } if (homeSections.isEmpty()) { - homes.value = Result.Error + homes.value = Error(Exception(Throwable("No items"))) } else { - homes.value = Result.Success(homeSections) + homes.value = Success(homeSections) } return homes } override suspend fun homeSections(): List { val homeSections = mutableListOf() - val sections = listOf( + val sections: List = listOf( + suggestionsHome(), topArtistsHome(), topAlbumsHome(), recentArtistsHome(), recentAlbumsHome(), - suggestionsHome(), favoritePlaylistHome() + // genresHome() ) for (section in sections) { if (section.arrayList.isNotEmpty()) { - println("${section.homeSection} -> ${section.arrayList.size}") homeSections.add(section) } } return homeSections } - override suspend fun playlists(): Home { - val playlist = playlistRepository.playlists() - return Home(playlist, TOP_ALBUMS) - } override suspend fun playlist(playlistId: Int) = playlistRepository.playlist(playlistId) + override suspend fun fetchPlaylistWithSongs(): List = + roomRepository.playlistWithSongs() + + override suspend fun playlistSongs(playlistWithSongs: PlaylistWithSongs): List = + playlistWithSongs.songs.map { + it.toSong() + } + + override fun playlistSongs(playlistEntity: PlaylistEntity): LiveData> = + roomRepository.getSongs(playlistEntity) + + override suspend fun insertSongs(songs: List) = + roomRepository.insertSongs(songs) + + override suspend fun checkPlaylistExists(playlistName: String): List = + roomRepository.checkPlaylistExists(playlistName) + + override suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = + roomRepository.createPlaylist(playlistEntity) + + override suspend fun fetchPlaylists(): List = roomRepository.playlists() + + override suspend fun deleteRoomPlaylist(playlists: List) = + roomRepository.deletePlaylistEntities(playlists) + + override suspend fun renameRoomPlaylist(playlistId: Int, name: String) = + roomRepository.renamePlaylistEntity(playlistId, name) + + override suspend fun deleteSongsInPlaylist(songs: List) = + roomRepository.deleteSongsInPlaylist(songs) + + override suspend fun removeSongFromPlaylist(songEntity: SongEntity) = + roomRepository.removeSongFromPlaylist(songEntity) + + override suspend fun deletePlaylistSongs(playlists: List) = + roomRepository.deletePlaylistSongs(playlists) + + override suspend fun favoritePlaylist(): PlaylistEntity = + roomRepository.favoritePlaylist(context.getString(R.string.favorites)) + + override suspend fun isFavoriteSong(songEntity: SongEntity): List = + roomRepository.isFavoriteSong(songEntity) + + override suspend fun addSongToHistory(currentSong: Song) = + roomRepository.addSongToHistory(currentSong) + + override suspend fun songPresentInHistory(currentSong: Song): HistoryEntity? = + roomRepository.songPresentInHistory(currentSong) + + override suspend fun updateHistorySong(currentSong: Song) = + roomRepository.updateHistorySong(currentSong) + + override suspend fun favoritePlaylistSongs(): List = + roomRepository.favoritePlaylistSongs(context.getString(R.string.favorites)) + + override suspend fun recentSongs(): List = lastAddedRepository.recentSongs() + + override suspend fun topPlayedSongs(): List = topPlayedRepository.topTracks() + + override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) = + roomRepository.insertSongInPlayCount(playCountEntity) + + override suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) = + roomRepository.updateSongInPlayCount(playCountEntity) + + override suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) = + roomRepository.deleteSongInPlayCount(playCountEntity) + + override suspend fun checkSongExistInPlayCount(songId: Int): List = + roomRepository.checkSongExistInPlayCount(songId) + + override suspend fun playCountSongs(): List = + roomRepository.playCountSongs() + + override suspend fun blackListPaths(): List = + roomRepository.blackListPaths() + + override fun observableHistorySongs(): LiveData> = + roomRepository.observableHistorySongs() + + override fun historySong(): List = + roomRepository.historySongs() + + override fun favorites(): LiveData> = + roomRepository.favoritePlaylistLiveData(context.getString(R.string.favorites)) + override suspend fun suggestionsHome(): Home { val songs = NotPlayedPlaylist().songs().shuffled().takeIf { it.size > 9 } ?: emptyList() - println(songs.size) - return Home(songs, SUGGESTIONS) + return Home(songs, SUGGESTIONS, R.string.suggestion_songs) } override suspend fun genresHome(): Home { val genres = genreRepository.genres().shuffled() - return Home(genres, GENRES) + return Home(genres, GENRES, R.string.genres) } + override suspend fun playlists(): Home { + val playlist = playlistRepository.playlists() + return Home(playlist, PLAYLISTS, R.string.playlists) + } override suspend fun recentArtistsHome(): Home { val artists = lastAddedRepository.recentArtists().take(5) - return Home(artists, RECENT_ARTISTS) + return Home(artists, RECENT_ARTISTS, R.string.recent_artists) } override suspend fun recentAlbumsHome(): Home { val albums = lastAddedRepository.recentAlbums().take(5) - return Home(albums, RECENT_ALBUMS) + return Home(albums, RECENT_ALBUMS, R.string.recent_albums) } override suspend fun topAlbumsHome(): Home { - val albums = playedTracksRepository.topAlbums().take(5) - return Home(albums, TOP_ALBUMS) + val albums = topPlayedRepository.topAlbums().take(5) + return Home(albums, TOP_ALBUMS, R.string.top_albums) } override suspend fun topArtistsHome(): Home { - val artists = playedTracksRepository.topArtists().take(5) - return Home(artists, TOP_ARTISTS) + val artists = topPlayedRepository.topArtists().take(5) + return Home(artists, TOP_ARTISTS, R.string.top_artists) } override suspend fun favoritePlaylistHome(): Home { - val playlists = - playlistRepository.favoritePlaylist(context.getString(R.string.favorites)).take(5) - val songs = if (playlists.isNotEmpty()) - PlaylistSongsLoader.getPlaylistSongList(context, playlists[0]) - else emptyList() - - return Home(songs, FAVOURITES) + val songs = favoritePlaylistSongs().map { + it.toSong() + } + return Home(songs, FAVOURITES, R.string.favorites) } override fun songsFlow(): Flow>> = flow { - emit(Result.Loading) + emit(Loading) val data = songRepository.songs() if (data.isEmpty()) { - emit(Result.Error) + emit(Error(Exception(Throwable("No items")))) } else { - emit(Result.Success(data)) + emit(Success(data)) } } override fun albumsFlow(): Flow>> = flow { - emit(Result.Loading) + emit(Loading) val data = albumRepository.albums() if (data.isEmpty()) { - emit(Result.Error) + emit(Error(Exception(Throwable("No items")))) } else { - emit(Result.Success(data)) + emit(Success(data)) } } override fun artistsFlow(): Flow>> = flow { - emit(Result.Loading) + emit(Loading) val data = artistRepository.artists() if (data.isEmpty()) { - emit(Result.Error) + emit(Error(Exception(Throwable("No items")))) } else { - emit(Result.Success(data)) + emit(Success(data)) } } override fun playlistsFlow(): Flow>> = flow { - emit(Result.Loading) + emit(Loading) val data = playlistRepository.playlists() if (data.isEmpty()) { - emit(Result.Error) + emit(Error(Exception(Throwable("No items")))) } else { - emit(Result.Success(data)) + emit(Success(data)) } } override fun genresFlow(): Flow>> = flow { - emit(Result.Loading) + emit(Loading) val data = genreRepository.genres() if (data.isEmpty()) { - emit(Result.Error) + emit(Error(Exception(Throwable("No items")))) } else { - emit(Result.Success(data)) + emit(Success(data)) } } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt new file mode 100644 index 00000000..16100176 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt @@ -0,0 +1,174 @@ +package code.name.monkey.retromusic.repository + +import androidx.annotation.WorkerThread +import androidx.lifecycle.LiveData +import code.name.monkey.retromusic.db.* +import code.name.monkey.retromusic.model.Song + + +interface RoomRepository { + fun historySongs(): List + fun favoritePlaylistLiveData(favorite: String): LiveData> + fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity) + fun observableHistorySongs(): LiveData> + fun getSongs(playlistEntity: PlaylistEntity): LiveData> + suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long + suspend fun checkPlaylistExists(playlistName: String): List + suspend fun playlists(): List + suspend fun playlistWithSongs(): List + suspend fun insertSongs(songs: List) + suspend fun deletePlaylistEntities(playlistEntities: List) + suspend fun renamePlaylistEntity(playlistId: Int, name: String) + suspend fun deleteSongsInPlaylist(songs: List) + suspend fun deletePlaylistSongs(playlists: List) + suspend fun favoritePlaylist(favorite: String): PlaylistEntity + suspend fun isFavoriteSong(songEntity: SongEntity): List + suspend fun removeSongFromPlaylist(songEntity: SongEntity) + suspend fun addSongToHistory(currentSong: Song) + suspend fun songPresentInHistory(song: Song): HistoryEntity? + suspend fun updateHistorySong(song: Song) + suspend fun favoritePlaylistSongs(favorite: String): List + suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) + suspend fun checkSongExistInPlayCount(songId: Int): List + suspend fun playCountSongs(): List + suspend fun insertBlacklistPath(blackListStoreEntities: List) + suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity) + suspend fun clearBlacklist() + suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity) + suspend fun blackListPaths(): List + suspend fun deleteSongs(songs: List) +} + +class RealRoomRepository( + private val playlistDao: PlaylistDao, + private val blackListStoreDao: BlackListStoreDao, + private val playCountDao: PlayCountDao, + private val historyDao: HistoryDao, + private val lyricsDao: LyricsDao +) : RoomRepository { + @WorkerThread + override suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = + playlistDao.createPlaylist(playlistEntity) + + @WorkerThread + override suspend fun checkPlaylistExists(playlistName: String): List = + playlistDao.isPlaylistExists(playlistName) + + @WorkerThread + override suspend fun playlists(): List = playlistDao.playlists() + + @WorkerThread + override suspend fun playlistWithSongs(): List = + playlistDao.playlistsWithSongs() + + @WorkerThread + override suspend fun insertSongs(songs: List) { + + playlistDao.insertSongsToPlaylist(songs) + } + + + override fun getSongs(playlistEntity: PlaylistEntity): LiveData> = + playlistDao.songsFromPlaylist(playlistEntity.playListId) + + override suspend fun deletePlaylistEntities(playlistEntities: List) = + playlistDao.deletePlaylists(playlistEntities) + + override suspend fun renamePlaylistEntity(playlistId: Int, name: String) = + playlistDao.renamePlaylist(playlistId, name) + + override suspend fun deleteSongsInPlaylist(songs: List) { + songs.forEach { + playlistDao.deleteSongFromPlaylist(it.playlistCreatorId, it.id) + } + } + + override suspend fun deletePlaylistSongs(playlists: List) = + playlists.forEach { + playlistDao.deletePlaylistSongs(it.playListId) + } + + override suspend fun favoritePlaylist(favorite: String): PlaylistEntity { + val playlist: PlaylistEntity? = playlistDao.isPlaylistExists(favorite).firstOrNull() + return if (playlist != null) { + playlist + } else { + createPlaylist(PlaylistEntity(favorite)) + playlistDao.isPlaylistExists(favorite).first() + } + } + + override suspend fun isFavoriteSong(songEntity: SongEntity): List = + playlistDao.isSongExistsInPlaylist( + songEntity.playlistCreatorId, + songEntity.id + ) + + override suspend fun removeSongFromPlaylist(songEntity: SongEntity) = + playlistDao.deleteSongFromPlaylist(songEntity.playlistCreatorId, songEntity.id) + + override suspend fun addSongToHistory(currentSong: Song) = + historyDao.insertSongInHistory(currentSong.toHistoryEntity(System.currentTimeMillis())) + + override suspend fun songPresentInHistory(song: Song): HistoryEntity? = + historyDao.isSongPresentInHistory(song.id) + + override suspend fun updateHistorySong(song: Song) = + historyDao.updateHistorySong(song.toHistoryEntity(System.currentTimeMillis())) + + override fun observableHistorySongs(): LiveData> = + historyDao.observableHistorySongs() + + override fun historySongs(): List = historyDao.historySongs() + + override fun favoritePlaylistLiveData(favorite: String): LiveData> = + playlistDao.favoritesSongsLiveData( + playlistDao.isPlaylistExists(favorite).first().playListId + ) + + override suspend fun favoritePlaylistSongs(favorite: String): List = + if (playlistDao.isPlaylistExists(favorite).isNotEmpty()) + playlistDao.favoritesSongs( + playlistDao.isPlaylistExists(favorite).first().playListId + ) else emptyList() + + override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) = + playCountDao.insertSongInPlayCount(playCountEntity) + + override suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) = + playCountDao.updateSongInPlayCount(playCountEntity) + + override suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) = + playCountDao.deleteSongInPlayCount(playCountEntity) + + override suspend fun checkSongExistInPlayCount(songId: Int): List = + playCountDao.checkSongExistInPlayCount(songId) + + override suspend fun playCountSongs(): List = + playCountDao.playCountSongs() + + override fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity) = + blackListStoreDao.insertBlacklistPath(blackListStoreEntity) + + override suspend fun insertBlacklistPath(blackListStoreEntities: List) = + blackListStoreDao.insertBlacklistPath(blackListStoreEntities) + + override suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity) = + blackListStoreDao.insertBlacklistPath(blackListStoreEntity) + + override suspend fun blackListPaths(): List = + blackListStoreDao.blackListPaths() + + override suspend fun deleteSongs(songs: List) { + songs.forEach { + playCountDao.deleteSong(it.id) + } + } + + override suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity) = + blackListStoreDao.deleteBlacklistPath(blackListStoreEntity) + + override suspend fun clearBlacklist() = blackListStoreDao.clearBlacklist() +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt index 430401a6..fcfded6a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/SongRepository.kt @@ -18,6 +18,7 @@ import android.content.Context import android.database.Cursor import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.Constants.IS_MUSIC import code.name.monkey.retromusic.Constants.baseProjection import code.name.monkey.retromusic.model.Song @@ -151,17 +152,21 @@ class RealSongRepository(private val context: Context) : SongRepository { } selectionFinal = selectionFinal + " AND " + MediaStore.Audio.Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000) - try { - return context.contentResolver.query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - baseProjection, - selectionFinal, - selectionValuesFinal, - sortOrder - ) - } catch (e: SecurityException) { - return null + + + val uri = if (VersionUtils.hasQ()) { + MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) + } else { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } + + return context.contentResolver.query( + uri, + baseProjection, + selectionFinal, + selectionValuesFinal, + sortOrder + ) } private fun generateBlacklistSelection( diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/TopPlayedRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/TopPlayedRepository.kt index 2ad30559..e522a092 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/TopPlayedRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/TopPlayedRepository.kt @@ -116,13 +116,13 @@ class RealTopPlayedRepository( private fun makeTopTracksCursorImpl(): SortedLongCursor? { // first get the top results ids from the internal database - val songs = + val cursor = SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS) - songs.use { localSongs -> + cursor.use { songs -> return makeSortedCursor( - localSongs, - localSongs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID) + songs, + songs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID) ) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index 64dfe785..eff55a60 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -84,11 +84,11 @@ import code.name.monkey.retromusic.util.MusicUtil; import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.RetroUtil; -import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCKSCREEN; +import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.GAPLESS_PLAYBACK; +import static code.name.monkey.retromusic.ConstantsKt.GAP_LESS_PLAYBACK; import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; /** @@ -727,7 +727,7 @@ public class MusicService extends Service implements @Override public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, @NonNull String key) { switch (key) { - case GAPLESS_PLAYBACK: + case GAP_LESS_PLAYBACK: if (sharedPreferences.getBoolean(key, false)) { prepareNext(); } else { @@ -736,7 +736,7 @@ public class MusicService extends Service implements } } break; - case ALBUM_ART_ON_LOCKSCREEN: + case ALBUM_ART_ON_LOCK_SCREEN: case BLURRED_ALBUM_ART: updateMediaSessionMetaData(); break; diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt index 1156418a..c7385fd8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt @@ -22,8 +22,8 @@ import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.drawable.Drawable import android.os.Build -import android.text.Html import androidx.core.app.NotificationCompat +import androidx.core.text.HtmlCompat import androidx.media.app.NotificationCompat.MediaStyle import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity @@ -132,9 +132,19 @@ class PlayingNotificationImpl : PlayingNotification() { .setLargeIcon(bitmapFinal) .setContentIntent(clickIntent) .setDeleteIntent(deleteIntent) - .setContentTitle(Html.fromHtml("" + song.title + "")) + .setContentTitle( + HtmlCompat.fromHtml( + "" + song.title + "", + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) .setContentText(song.artistName) - .setSubText(Html.fromHtml("" + song.albumName + "")) + .setSubText( + HtmlCompat.fromHtml( + "" + song.albumName + "", + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + ) .setOngoing(isPlaying) .setShowWhen(false) .addAction(toggleFavorite) diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt index 6cbeb1fb..8b12c41e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/AppRater.kt @@ -19,7 +19,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.net.Uri -import android.widget.Toast import code.name.monkey.retromusic.R import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.play.core.review.ReviewManagerFactory @@ -71,8 +70,7 @@ object AppRater { val reviewInfo = request.result manager.launchReviewFlow(context as Activity, reviewInfo).addOnCompleteListener { if (it.isSuccessful) { - Toast.makeText(context, "Thanks for the feedback", Toast.LENGTH_SHORT) - .show() + //Toast.makeText(context, "Thanks for the feedback", Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java index a1fe1e22..c8115e81 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/AutoGeneratedPlaylistBitmap.java @@ -162,7 +162,6 @@ public class AutoGeneratedPlaylistBitmap { } - ; Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start)); if (round) return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40); diff --git a/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java b/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java index 1329e9ef..0f796141 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/BitmapEditor.java @@ -83,16 +83,16 @@ public final class BitmapEditor { int wh = w * h; int div = radius + radius + 1; - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; - int a[] = new int[wh]; + int[] r = new int[wh]; + int[] g = new int[wh]; + int[] b = new int[wh]; + int[] a = new int[wh]; int rsum, gsum, bsum, asum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; + int[] vmin = new int[Math.max(w, h)]; int divsum = (div + 1) >> 1; divsum *= divsum; - int dv[] = new int[256 * divsum]; + int[] dv = new int[256 * divsum]; for (i = 0; i < 256 * divsum; i++) { dv[i] = (i / divsum); } @@ -295,7 +295,7 @@ public final class BitmapEditor { public static boolean PerceivedBrightness(int will_White, int[] c) { double TBT = Math.sqrt(c[0] * c[0] * .241 + c[1] * c[1] * .691 + c[2] * c[2] * .068); // Log.d("themee",TBT+""); - return (TBT > will_White) ? false : true; + return !(TBT > will_White); } public static int[] getAverageColorRGB(Bitmap bitmap) { @@ -404,15 +404,15 @@ public final class BitmapEditor { int wh = w * h; int div = radius + radius + 1; - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; + int[] r = new int[wh]; + int[] g = new int[wh]; + int[] b = new int[wh]; int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; + int[] vmin = new int[Math.max(w, h)]; int divsum = (div + 1) >> 1; divsum *= divsum; - int dv[] = new int[256 * divsum]; + int[] dv = new int[256 * divsum]; for (i = 0; i < 256 * divsum; i++) { dv[i] = (i / divsum); } @@ -635,8 +635,7 @@ public final class BitmapEditor { public static boolean TrueIfBitmapBigger(Bitmap bitmap, int size) { int sizeBitmap = (bitmap.getHeight() > bitmap.getWidth()) ? bitmap.getHeight() : bitmap.getWidth(); - if (sizeBitmap > size) return true; - return false; + return sizeBitmap > size; } public static Bitmap GetRoundedBitmapWithBlurShadow(Bitmap original, int paddingTop, int paddingBottom, int paddingLeft, int paddingRight) { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java new file mode 100644 index 00000000..e4133392 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtil.java @@ -0,0 +1,58 @@ +package code.name.monkey.retromusic.util; + +import android.graphics.Bitmap; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; +import androidx.palette.graphics.Palette; + +import java.util.Collections; +import java.util.Comparator; + +public class ColorUtil { + + @Nullable + public static Palette generatePalette(Bitmap bitmap) { + if (bitmap == null) return null; + return Palette.from(bitmap).generate(); + } + + @ColorInt + public static int getColor(@Nullable Palette palette, int fallback) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch().getRgb(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch().getRgb(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch().getRgb(); + } else if (!palette.getSwatches().isEmpty()) { + return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); + } + } + return fallback; + } + + private static class SwatchComparator implements Comparator { + private static SwatchComparator sInstance; + + static SwatchComparator getInstance() { + if (sInstance == null) { + sInstance = new SwatchComparator(); + } + return sInstance; + } + + @Override + public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { + return lhs.getPopulation() - rhs.getPopulation(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FilePathUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/FilePathUtil.kt new file mode 100644 index 00000000..da30e71c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/FilePathUtil.kt @@ -0,0 +1,16 @@ +package code.name.monkey.retromusic.util + +import android.os.Environment +import java.io.File + +object FilePathUtil { + fun blacklistFilePaths(): List { + return listOf( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS), + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES), + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS) + ).map { + FileUtil.safeGetCanonicalPath(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java index 1ea5adff..cbf13f2b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java @@ -245,13 +245,9 @@ public final class FileUtil { .equals(android.os.Environment.MEDIA_MOUNTED); Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable(); - if (isSDSupportedDevice && isSDPresent) { - // yes SD-card is present - return true; - } else { - return false; - // Sorry - } + // yes SD-card is present + // Sorry + return isSDSupportedDevice && isSDPresent; } public static File safeGetCanonicalFile(File file) { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java index 0de666ef..ce182217 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java @@ -26,7 +26,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; /** * Created by hefuyi on 2016/11/8. @@ -102,7 +102,7 @@ public class LyricUtil { } private static String getLrcOriginalPath(String filePath) { - return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1, filePath.length()), "lrc"); + return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc"); } @NonNull @@ -110,16 +110,9 @@ public class LyricUtil { if (str == null || str.length() == 0) { return null; } - try { - byte[] encode = str.getBytes("UTF-8"); - // base64 解密 - return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), "UTF-8"); - - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - - return null; + byte[] encode = str.getBytes(StandardCharsets.UTF_8); + // base64 解密 + return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8); } @NonNull diff --git a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt index 130cee69..a29663a1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt @@ -4,21 +4,25 @@ import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent +import android.database.Cursor import android.net.Uri import android.os.Environment import android.provider.BaseColumns import android.provider.MediaStore import android.text.TextUtils +import android.util.Log import android.widget.Toast import androidx.core.content.FileProvider import androidx.fragment.app.FragmentActivity import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics import code.name.monkey.retromusic.repository.RealPlaylistRepository +import code.name.monkey.retromusic.repository.RealSongRepository import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.service.MusicService import org.jaudiotagger.audio.AudioFileIO @@ -110,7 +114,7 @@ object MusicUtil : KoinComponent { } fun getLyrics(song: Song): String? { - var lyrics: String? = null + var lyrics: String? = "No lyrics found" val file = File(song.data) try { lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS) @@ -150,7 +154,7 @@ object MusicUtil : KoinComponent { } false } - if (files != null && files.size > 0) { + if (files != null && files.isNotEmpty()) { for (f in files) { try { val newLyrics = @@ -189,6 +193,13 @@ object MusicUtil : KoinComponent { ) } + fun playlistInfoString( + context: Context, + songs: List + ): String { + return getSongCountString(context, songs.size) + } + fun getReadableDurationString(songDurationMillis: Long): String? { var minutes = songDurationMillis / 1000 / 60 val seconds = songDurationMillis / 1000 % 60 @@ -302,7 +313,7 @@ object MusicUtil : KoinComponent { context: Context, playlist: Playlist ): Boolean { - return playlist.name != null && playlist.name == context.getString(R.string.favorites) + return playlist.name == context.getString(R.string.favorites) } fun toggleFavorite(context: Context, song: Song) { @@ -341,7 +352,7 @@ object MusicUtil : KoinComponent { BaseColumns._ID, MediaStore.MediaColumns.DATA ) // Split the query into multiple batches, and merge the resulting cursors - var batchStart = 0 + var batchStart: Int var batchEnd = 0 val batchSize = 1000000 / 10 // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID @@ -413,6 +424,78 @@ object MusicUtil : KoinComponent { .show() callback?.run() } + } } + + fun deleteTracks(context: Context, songs: List) { + val projection = arrayOf( + BaseColumns._ID, MediaStore.MediaColumns.DATA + ) + val selection = StringBuilder() + selection.append(BaseColumns._ID + " IN (") + for (i in songs.indices) { + selection.append(songs[i].id) + if (i < songs.size - 1) { + selection.append(",") + } + } + selection.append(")") + var deletedCount = 0 + try { + val cursor: Cursor? = context.contentResolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null + ) + if (cursor != null) { + // Step 1: Remove selected tracks from the current playlist, as well + // as from the album art cache + cursor.moveToFirst() + while (!cursor.isAfterLast) { + val id: Int = cursor.getInt(0) + val song: Song = RealSongRepository(context).song(id) + removeFromQueue(song) + cursor.moveToNext() + } + + + // Step 2: Remove files from card + cursor.moveToFirst() + while (!cursor.isAfterLast) { + val id: Int = cursor.getInt(0) + val name: String = cursor.getString(1) + try { // File.delete can throw a security exception + val f = File(name) + if (f.delete()) { + // Step 3: Remove selected track from the database + context.contentResolver.delete( + ContentUris.withAppendedId( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + id.toLong() + ), null, null + ) + deletedCount++ + } else { + // I'm not sure if we'd ever get here (deletion would + // have to fail, but no exception thrown) + Log.e("MusicUtils", "Failed to delete file $name") + } + cursor.moveToNext() + } catch (ex: SecurityException) { + cursor.moveToNext() + } catch (e: NullPointerException) { + Log.e("MusicUtils", "Failed to find file $name") + } + } + cursor.close() + } + Toast.makeText( + context, + context.getString(R.string.deleted_x_songs, deletedCount), + Toast.LENGTH_SHORT + ).show() + } catch (ignored: SecurityException) { + } + } + } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java index 84b596f5..cc2dad88 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.db.PlaylistWithSongs; import code.name.monkey.retromusic.helper.M3UWriter; import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.PlaylistSong; @@ -195,7 +196,7 @@ public class PlaylistsUtil { final int playlistId = songs.get(0).getPlaylistId(); Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( "external", playlistId); - String selectionArgs[] = new String[songs.size()]; + String[] selectionArgs = new String[songs.size()]; for (int i = 0; i < selectionArgs.length; i++) { selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList()); } @@ -247,7 +248,11 @@ public class PlaylistsUtil { } public static File savePlaylist(Context context, Playlist playlist) throws IOException { - return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); + return M3UWriter.write(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); + } + + public static File savePlaylistWithSongs(Context context, PlaylistWithSongs playlist) throws IOException { + return M3UWriter.writeIO(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); } public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) { diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt index 312bea1b..88333684 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt @@ -35,7 +35,6 @@ object PreferenceUtil { CategoryInfo(CategoryInfo.Category.Artists, true), CategoryInfo(CategoryInfo.Category.Playlists, true), CategoryInfo(CategoryInfo.Category.Genres, false), - CategoryInfo(CategoryInfo.Category.Queue, false), CategoryInfo(CategoryInfo.Category.Folder, false) ) @@ -93,7 +92,7 @@ object PreferenceUtil { } } - val languageCode get() = sharedPreferences.getString(LANGUAGE_NAME, "auto") + val languageCode: String get() = sharedPreferences.getString(LANGUAGE_NAME, "auto") ?: "auto" var userName get() = sharedPreferences.getString( @@ -243,7 +242,7 @@ object PreferenceUtil { val isAlbumArtOnLockScreen get() = sharedPreferences.getBoolean( - ALBUM_ART_ON_LOCKSCREEN, false + ALBUM_ART_ON_LOCK_SCREEN, false ) val isAudioDucking @@ -292,7 +291,7 @@ object PreferenceUtil { val isGapLessPlayback get() = sharedPreferences.getBoolean( - GAPLESS_PLAYBACK, false + GAP_LESS_PLAYBACK, false ) val isAdaptiveColor @@ -471,7 +470,7 @@ object PreferenceUtil { var artistGridSizeLand get() = sharedPreferences.getInt( - ALBUM_GRID_SIZE_LAND, + ARTIST_GRID_SIZE_LAND, App.getContext().getIntRes(R.integer.default_grid_columns_land) ) set(value) = sharedPreferences.edit { @@ -528,7 +527,7 @@ object PreferenceUtil { get() { val folderPath = FoldersFragment.getDefaultStartDirectory().path val filePath: String = sharedPreferences.getStringOrDefault(START_DIRECTORY, folderPath) - return File(filePath) ?: File(FoldersFragment.getDefaultStartDirectory().path) + return File(filePath) } set(value) = sharedPreferences.edit { putString( diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java index 6bc801c0..f1b1c665 100755 --- a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java @@ -70,7 +70,7 @@ public class RetroUtil { } public static String formatValue(float value) { - String arr[] = {"", "K", "M", "B", "T", "P", "E"}; + String[] arr = {"", "K", "M", "B", "T", "P", "E"}; int index = 0; while ((value / 1000) >= 1) { value = value / 1000; diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java index 4705f820..b22bab5d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageUtils.java @@ -36,11 +36,62 @@ public class ImageUtils { private static final int ALPHA_TOLERANCE = 50; // Size of the smaller bitmap we're actually going to scan. private static final int COMPACT_BITMAP_SIZE = 64; // pixels + private final Matrix mTempMatrix = new Matrix(); private int[] mTempBuffer; private Bitmap mTempCompactBitmap; private Canvas mTempCompactBitmapCanvas; private Paint mTempCompactBitmapPaint; - private final Matrix mTempMatrix = new Matrix(); + + /** + * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect + * gray"; if all three channels are approximately equal, this will return true. + *

+ * Note that really transparent colors are always grayscale. + */ + public static boolean isGrayscale(int color) { + int alpha = 0xFF & (color >> 24); + if (alpha < ALPHA_TOLERANCE) { + return true; + } + int r = 0xFF & (color >> 16); + int g = 0xFF & (color >> 8); + int b = 0xFF & color; + return Math.abs(r - g) < TOLERANCE + && Math.abs(r - b) < TOLERANCE + && Math.abs(g - b) < TOLERANCE; + } + + /** + * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. + */ + public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, + int maxHeight) { + if (drawable == null) { + return null; + } + int originalWidth = drawable.getIntrinsicWidth(); + int originalHeight = drawable.getIntrinsicHeight(); + if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) && + (drawable instanceof BitmapDrawable)) { + return ((BitmapDrawable) drawable).getBitmap(); + } + if (originalHeight <= 0 || originalWidth <= 0) { + return null; + } + // create a new bitmap, scaling down to fit the max dimensions of + // a large notification icon if necessary + float ratio = Math.min((float) maxWidth / (float) originalWidth, + (float) maxHeight / (float) originalHeight); + ratio = Math.min(1.0f, ratio); + int scaledWidth = (int) (ratio * originalWidth); + int scaledHeight = (int) (ratio * originalHeight); + Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); + // and paint our app bitmap on it + Canvas canvas = new Canvas(result); + drawable.setBounds(0, 0, scaledWidth, scaledHeight); + drawable.draw(canvas); + return result; + } /** * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect @@ -93,55 +144,4 @@ public class ImageUtils { mTempBuffer = new int[size]; } } - - /** - * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect - * gray"; if all three channels are approximately equal, this will return true. - *

- * Note that really transparent colors are always grayscale. - */ - public static boolean isGrayscale(int color) { - int alpha = 0xFF & (color >> 24); - if (alpha < ALPHA_TOLERANCE) { - return true; - } - int r = 0xFF & (color >> 16); - int g = 0xFF & (color >> 8); - int b = 0xFF & color; - return Math.abs(r - g) < TOLERANCE - && Math.abs(r - b) < TOLERANCE - && Math.abs(g - b) < TOLERANCE; - } - - /** - * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. - */ - public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, - int maxHeight) { - if (drawable == null) { - return null; - } - int originalWidth = drawable.getIntrinsicWidth(); - int originalHeight = drawable.getIntrinsicHeight(); - if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) && - (drawable instanceof BitmapDrawable)) { - return ((BitmapDrawable) drawable).getBitmap(); - } - if (originalHeight <= 0 || originalWidth <= 0) { - return null; - } - // create a new bitmap, scaling down to fit the max dimensions of - // a large notification icon if necessary - float ratio = Math.min((float) maxWidth / (float) originalWidth, - (float) maxHeight / (float) originalHeight); - ratio = Math.min(1.0f, ratio); - int scaledWidth = (int) (ratio * originalWidth); - int scaledHeight = (int) (ratio * originalHeight); - Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); - // and paint our app bitmap on it - Canvas canvas = new Canvas(result); - drawable.setBounds(0, 0, scaledWidth, scaledHeight); - drawable.draw(canvas); - return result; - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/AccentIcon.kt b/app/src/main/java/code/name/monkey/retromusic/views/AccentIcon.kt new file mode 100644 index 00000000..c48c3c41 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/AccentIcon.kt @@ -0,0 +1,17 @@ +package code.name.monkey.retromusic.views + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import code.name.monkey.retromusic.extensions.accentColor + +class AccentIcon @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1 +) : AppCompatImageView(context, attrs, defStyleAttr) { + init { + imageTintList = ColorStateList.valueOf(accentColor()) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/ColorIconsImageView.kt b/app/src/main/java/code/name/monkey/retromusic/views/ColorIconsImageView.kt index 378ddff4..fc2d793d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/ColorIconsImageView.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/ColorIconsImageView.kt @@ -39,7 +39,7 @@ class ColorIconsImageView @JvmOverloads constructor( val attributes = context.obtainStyledAttributes(attrs, R.styleable.ColorIconsImageView, 0, 0) val color = - attributes.getColor(R.styleable.ColorIconsImageView_iconBackgroundColor, Color.RED); + attributes.getColor(R.styleable.ColorIconsImageView_iconBackgroundColor, Color.RED) setIconBackgroundColor(color) attributes.recycle() } diff --git a/app/src/main/java/code/name/monkey/retromusic/views/RetroShapeableImageView.kt b/app/src/main/java/code/name/monkey/retromusic/views/RetroShapeableImageView.kt index df479136..e00946d4 100644 --- a/app/src/main/java/code/name/monkey/retromusic/views/RetroShapeableImageView.kt +++ b/app/src/main/java/code/name/monkey/retromusic/views/RetroShapeableImageView.kt @@ -32,7 +32,7 @@ class RetroShapeableImageView @JvmOverloads constructor( val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1) val cornerSize = - typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f); + typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f) updateCornerSize(cornerSize) typedArray.recycle() } diff --git a/app/src/main/res/drawable/ic_account.xml b/app/src/main/res/drawable/ic_account.xml index 4414a60e..65de32cf 100644 --- a/app/src/main/res/drawable/ic_account.xml +++ b/app/src/main/res/drawable/ic_account.xml @@ -19,6 +19,6 @@ android:viewportHeight="24"> + android:fillColor="@android:color/white" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM7.07,18.28c0.43,-0.9 3.05,-1.78 4.93,-1.78s4.51,0.88 4.93,1.78C15.57,19.36 13.86,20 12,20s-3.57,-0.64 -4.93,-1.72zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33C4.62,15.49 4,13.82 4,12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83zM12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z" /> \ No newline at end of file diff --git a/app/src/main/res/font/pacifico.xml b/app/src/main/res/font/pacifico.xml deleted file mode 100644 index 6cff80c8..00000000 --- a/app/src/main/res/font/pacifico.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/app/src/main/res/layout-land/fragment_banner_home.xml b/app/src/main/res/layout-land/fragment_banner_home.xml index 52043a54..97e02abb 100644 --- a/app/src/main/res/layout-land/fragment_banner_home.xml +++ b/app/src/main/res/layout-land/fragment_banner_home.xml @@ -87,7 +87,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:padding="0dp" - android:textAppearance="@style/TextViewHeadline5" + android:textAppearance="@style/TextViewHeadline6" android:textColor="@color/md_white_1000" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/userImage" diff --git a/app/src/main/res/layout-land/fragment_home.xml b/app/src/main/res/layout-land/fragment_home.xml index 9da4068d..5e50ad75 100644 --- a/app/src/main/res/layout-land/fragment_home.xml +++ b/app/src/main/res/layout-land/fragment_home.xml @@ -57,7 +57,7 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:padding="0dp" - android:textAppearance="@style/TextViewHeadline5" + android:textAppearance="@style/TextViewHeadline6" android:textColor="?android:attr/textColorPrimary" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/userImage" diff --git a/app/src/main/res/layout/abs_playlists.xml b/app/src/main/res/layout/abs_playlists.xml index a0b8d1d2..73df4233 100644 --- a/app/src/main/res/layout/abs_playlists.xml +++ b/app/src/main/res/layout/abs_playlists.xml @@ -6,7 +6,6 @@ android:orientation="vertical" android:paddingBottom="12dp"> - + app:lrcTextSize="28sp" /> diff --git a/app/src/main/res/layout/card_credit.xml b/app/src/main/res/layout/card_credit.xml index b80c6e8a..f7900700 100644 --- a/app/src/main/res/layout/card_credit.xml +++ b/app/src/main/res/layout/card_credit.xml @@ -2,9 +2,9 @@ + tools:text="@tools:sample/full_names" /> + tools:text="@tools:sample/full_names" /> - + app:layout_constraintWidth_default="spread" + tools:background="@color/md_red_500"> + app:layout_constraintTop_toTopOf="@id/queueIcon" /> + android:id="@+id/appNameText" + android:textAppearance="@style/TextViewHeadline6" + android:textStyle="bold" /> - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_settings.xml b/app/src/main/res/layout/fragment_main_settings.xml index 421dc3da..4b12528e 100644 --- a/app/src/main/res/layout/fragment_main_settings.xml +++ b/app/src/main/res/layout/fragment_main_settings.xml @@ -14,11 +14,11 @@ + app:cardCornerRadius="8dp"> @@ -70,4 +71,12 @@ android:textColor="?android:attr/textColorSecondary" tools:visibility="visible" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/home_content.xml b/app/src/main/res/layout/home_content.xml index f97294c1..8062f058 100644 --- a/app/src/main/res/layout/home_content.xml +++ b/app/src/main/res/layout/home_content.xml @@ -28,27 +28,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_album_card.xml b/app/src/main/res/layout/item_album_card.xml index 21ebef3d..e7e0bd33 100644 --- a/app/src/main/res/layout/item_album_card.xml +++ b/app/src/main/res/layout/item_album_card.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:padding="2dp" android:background="?attr/rectSelector" android:orientation="vertical"> diff --git a/app/src/main/res/layout/item_artist.xml b/app/src/main/res/layout/item_artist.xml index fadcff2f..0d92b7ee 100644 --- a/app/src/main/res/layout/item_artist.xml +++ b/app/src/main/res/layout/item_artist.xml @@ -2,7 +2,7 @@ + tools:text="@tools:sample/full_names" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_list.xml b/app/src/main/res/layout/item_list.xml index 30a61347..fac82e1e 100755 --- a/app/src/main/res/layout/item_list.xml +++ b/app/src/main/res/layout/item_list.xml @@ -89,6 +89,7 @@ android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" + android:textAppearance="@style/TextViewBody2" android:textColor="?android:attr/textColorSecondary" tools:text="@tools:sample/full_names" /> diff --git a/app/src/main/res/layout/item_list_no_image.xml b/app/src/main/res/layout/item_list_no_image.xml index 4e28bbf8..bf6ffc26 100644 --- a/app/src/main/res/layout/item_list_no_image.xml +++ b/app/src/main/res/layout/item_list_no_image.xml @@ -18,7 +18,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="2dp" - android:background="?rectSelector" + android:background="?attr/rectSelector" android:minHeight="64dp" android:padding="14dp" tools:ignore="UnusedAttribute"> @@ -40,6 +40,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:textAppearance="@style/TextViewBody2" + android:textColor="?android:textColorSecondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" diff --git a/app/src/main/res/layout/loading.xml b/app/src/main/res/layout/loading.xml index 54c6d3c1..6ae87157 100644 --- a/app/src/main/res/layout/loading.xml +++ b/app/src/main/res/layout/loading.xml @@ -20,7 +20,7 @@ android:padding="14dp"> diff --git a/app/src/main/res/layout/lyrics_dialog.xml b/app/src/main/res/layout/lyrics_dialog.xml new file mode 100644 index 00000000..47f15438 --- /dev/null +++ b/app/src/main/res/layout/lyrics_dialog.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/section_recycler_view.xml b/app/src/main/res/layout/section_recycler_view.xml index 852c6c48..a86a9cc0 100644 --- a/app/src/main/res/layout/section_recycler_view.xml +++ b/app/src/main/res/layout/section_recycler_view.xml @@ -5,8 +5,7 @@ android:id="@+id/recentArtistContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="12dp"> + android:orientation="vertical" > - + app:srcCompat="@drawable/ic_arrow_forward" /> - diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 1458c415..83b9ea93 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -16,10 +16,82 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".DrawerActivity"> - + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index ef3cfc0a..1986a05c 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 00000000..94cbef3c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index ba687419..64bcc529 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 6c46ac4c..1986a05c 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index 4e45638f..08d7ea8a 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 00000000..41be5687 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 5fe585e3..cb23a30b 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 00668b42..08d7ea8a 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 23394228..1a3f47f9 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 00000000..c775d1c8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index bcaf1a42..d719d157 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index c998e206..1a3f47f9 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 9142374e..4f5dbff3 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..a42cd921 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 451e815e..314e49d5 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 663d2450..4f5dbff3 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index e78b0afd..3ccac23e 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..0282220d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 3f076e2d..aac99848 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 8b5d5d8d..3ccac23e 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/navigation/library_graph.xml b/app/src/main/res/navigation/library_graph.xml index d95d705c..3d670797 100644 --- a/app/src/main/res/navigation/library_graph.xml +++ b/app/src/main/res/navigation/library_graph.xml @@ -30,11 +30,6 @@ android:name="code.name.monkey.retromusic.fragments.playlists.PlaylistsFragment" tools:layout="@layout/fragment_main_activity_recycler_view" /> - - + tools:layout="@layout/fragment_banner_home" /> + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_graph.xml b/app/src/main/res/navigation/main_graph.xml index beead4b8..009217c2 100644 --- a/app/src/main/res/navigation/main_graph.xml +++ b/app/src/main/res/navigation/main_graph.xml @@ -5,10 +5,6 @@ android:id="@+id/retro_graph" app:startDestination="@id/libraryFragment"> - - - - + app:argType="code.name.monkey.retromusic.db.PlaylistWithSongs" /> - - - + tools:layout="@layout/fragment_library" /> - - - @font/pacifico - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f82fda09..018bdc9f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -883,4 +883,7 @@ Hello blank fragment Done + Import playlist + It imports all playlists listed in the Android Media Store with songs, if the playlists already exists, the songs will get merged. + Import diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2f11c158..01529cc0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -63,12 +63,6 @@ ?android:attr/textColorPrimary - - + + @@ -237,4 +238,16 @@ @dimen/button_padding_vertical @dimen/button_padding_vertical + + + + + diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt index 92cbbf41..641bce19 100644 --- a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt @@ -42,4 +42,19 @@ object VersionUtils { fun hasOreo(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O } + + /** + * @return true if device is running API >= 27 + */ + fun hasP(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + } + + /** + * @return true if device is running API >= 28 + */ + @JvmStatic + fun hasQ(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + } } diff --git a/build.gradle b/build.gradle index 194f9bfd..94e3964a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.10' repositories { jcenter() google() diff --git a/gradle.properties b/gradle.properties index 181b62cc..a25e163f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx2048M +org.gradle.jvmargs=-Xmx4608m org.gradle.daemon=true org.gradle.parallel=true jvmArgs='-Xmx2048m'
App version" + versionName + "
App version" + versionName + "
App version code" + versionCode + "
Android build version" + buildVersion + "
Android release version" + releaseVersion + "