diff --git a/.gitignore b/.gitignore index 37f05b7f5..6a32941f0 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/app/build.gradle b/app/build.gradle index 6b4cc101a..692677dd2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,20 +2,11 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: "ru.cleverpumpkin.proguard-dictionaries-generator" apply plugin: "androidx.navigation.safeargs.kotlin" -proguardDictionaries { - dictionaryNames = [ - "build/class-dictionary", - "build/package-dictionary", - "build/obfuscation-dictionary" - ] -} - android { compileSdkVersion 29 - buildToolsVersion = '30.0.0' + buildToolsVersion = '30.0.1' defaultConfig { minSdkVersion 21 @@ -25,7 +16,7 @@ android { vectorDrawables.useSupportLibrary = true applicationId 'io.github.muntashirakon.Music' - versionCode 10443 + versionCode 10444 versionName '3.5.10' multiDexEnabled true @@ -73,66 +64,51 @@ android { 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-rc1' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + def nav_version = "2.3.0" + implementation "androidx.navigation:navigation-runtime-ktx:$nav_version" + 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-alpha02' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - - 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.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 '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" @@ -142,7 +118,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/proguard-rules.pro b/app/proguard-rules.pro index 5e2e2be56..a7f474df0 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -41,25 +41,14 @@ public *; } --keep class !android.support.v7.internal.view.menu.**,** {*;} -dontwarn -ignorewarnings --keep public class android.support.design.widget.BottomNavigationView { *; } --keep public class android.support.design.internal.BottomNavigationMenuView { *; } --keep public class android.support.design.internal.BottomNavigationPresenter { *; } --keep public class android.support.design.internal.BottomNavigationItemView { *; } - #-dontwarn android.support.v8.renderscript.* #-keepclassmembers class android.support.v8.renderscript.RenderScript { # native *** rsn*(...); # native *** n*(...); #} -#-keep class org.jaudiotagger.** { *; } - - --obfuscationdictionary build/obfuscation-dictionary.txt --classobfuscationdictionary build/class-dictionary.txt --packageobfuscationdictionary build/package-dictionary.txt +#-keep class org.jaudiotagger.** { *; } \ 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 08c9ac789..000000000 --- a/app/release/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "io.github.muntashirakon.Music", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "properties": [], - "versionCode": 10443, - "versionName": "3.5.10", - "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 000000000..96619df92 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 000000000..80497666e 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 000000000..1543660da 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 000000000..ab605f9e2 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 000000000..fd818d6f5 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 000000000..e2c69c3fb 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 000000000..7bbc8513b --- /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 000000000..81154a395 --- /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 d90e768d1..26f52498e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,6 +115,7 @@ + + - diff --git a/app/src/main/assets/contributors.json b/app/src/main/assets/contributors.json index 2efd24509..b76201619 100644 --- a/app/src/main/assets/contributors.json +++ b/app/src/main/assets/contributors.json @@ -13,13 +13,13 @@ }, { "name": "Daksh P. Jain", - "summary": "Telegram group maintainer", - "link": "https://dakshpjain.eu.org", + "summary": "Support Representative & Moderator", + "link": "https://daksh.eu.org", "profile_image": "https://i.imgur.com/fnYpg65.jpg" }, { "name": "Milind Goel", - "summary": "Github & Telegram maintainer", + "summary": "Support Representative & Moderator", "link": "https://t.me/MilindGoel15", "profile_image": "https://i.imgur.com/Bz4De21_d.jpg" } diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index f9c45db6b..a4a872d34 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/io/github/muntashirakon/music/Constants.kt b/app/src/main/java/io/github/muntashirakon/music/Constants.kt index 85c8c327b..5e3755eb3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/Constants.kt +++ b/app/src/main/java/io/github/muntashirakon/music/Constants.kt @@ -30,7 +30,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/io/github/muntashirakon/music/HomeSection.kt b/app/src/main/java/io/github/muntashirakon/music/HomeSection.kt index 237e5bca6..1db2e52cc 100644 --- a/app/src/main/java/io/github/muntashirakon/music/HomeSection.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/MainModule.kt b/app/src/main/java/io/github/muntashirakon/music/MainModule.kt index af439dcb6..72a808366 100644 --- a/app/src/main/java/io/github/muntashirakon/music/MainModule.kt +++ b/app/src/main/java/io/github/muntashirakon/music/MainModule.kt @@ -1,5 +1,12 @@ package io.github.muntashirakon.music +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 io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.albums.AlbumDetailsViewModel import io.github.muntashirakon.music.fragments.artists.ArtistDetailsViewModel @@ -7,17 +14,103 @@ import io.github.muntashirakon.music.fragments.genres.GenreDetailsViewModel import io.github.muntashirakon.music.fragments.playlists.PlaylistDetailsViewModel import io.github.muntashirakon.music.fragments.search.SearchViewModel import io.github.muntashirakon.music.model.Genre -import io.github.muntashirakon.music.model.Playlist -import io.github.muntashirakon.music.network.networkModule +import io.github.muntashirakon.music.network.* import io.github.muntashirakon.music.repository.* +import io.github.muntashirakon.music.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 { @@ -73,21 +162,21 @@ private val viewModules = module { LibraryViewModel(get()) } - viewModel { (albumId: Int) -> + viewModel { (albumId: Long) -> AlbumDetailsViewModel( get(), albumId ) } - viewModel { (artistId: Int) -> + viewModel { (artistId: Long) -> ArtistDetailsViewModel( get(), artistId ) } - 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/io/github/muntashirakon/music/activities/DriveModeActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/DriveModeActivity.kt index 51c17e796..6435aec1c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/DriveModeActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/activities/MainActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt index 665823529..00e04b216 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt @@ -3,30 +3,23 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.* import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.extensions.findNavController -import io.github.muntashirakon.music.fragments.LibraryViewModel -import io.github.muntashirakon.music.helper.MusicPlayerRemote.openAndShuffleQueue -import io.github.muntashirakon.music.helper.MusicPlayerRemote.openQueue -import io.github.muntashirakon.music.helper.MusicPlayerRemote.playFromUri -import io.github.muntashirakon.music.helper.MusicPlayerRemote.shuffleMode +import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.SearchQueryHelper.getSongs import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.repository.PlaylistSongsLoader.getPlaylistSongList -import io.github.muntashirakon.music.repository.Repository +import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.service.MusicService -import io.github.muntashirakon.music.util.AppRater.appLaunched +import io.github.muntashirakon.music.util.AppRater import io.github.muntashirakon.music.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,11 +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 { return wrapSlidingMusicPanel(R.layout.activity_main_content) } @@ -47,13 +35,15 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis override fun onCreate(savedInstanceState: Bundle?) { setDrawUnderStatusBar() super.onCreate(savedInstanceState) + if (!hasPermissions()) { + findNavController(R.id.fragment_container).navigate(R.id.permissionFragment) + } setStatusbarColorAuto() setNavigationbarColorAuto() setLightNavigationBar(true) setTaskDescriptionColorAuto() hideStatusBar() - appLaunched(this) - addMusicServiceEventListener(libraryViewModel) + AppRater.appLaunched(this) updateTabs() } @@ -72,24 +62,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis } } - override fun onDestroy() { - super.onDestroy() - PreferenceUtil.unregisterOnSharedPreferenceChangedListener(this) - } - - override fun requestPermissions() { - if (!blockRequestPermissions) { - super.requestPermissions() - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (!hasPermissions()) { - requestPermissions() - } - } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES ) { @@ -99,64 +71,71 @@ 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) - 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) - 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()) - } + handlePlaybackIntent(intent) } - private fun parseIdFromIntent( + 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 + } + if (uri != null && uri.toString().isNotEmpty()) { + MusicPlayerRemote.playFromUri(uri) + handled = true + } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { + val id = parseLongFromIntent(intent, "playlistId", "playlist") + if (id >= 0L) { + 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 = parseLongFromIntent(intent, "albumId", "album") + if (id >= 0L) { + 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 = parseLongFromIntent(intent, "artistId", "artist") + if (id >= 0L) { + val position: Int = intent.getIntExtra("position", 0) + MusicPlayerRemote.openQueue( + libraryViewModel.artistById(id).songs, + position, + true + ) + handled = true + } + } + if (handled) { + setIntent(Intent()) + } + } + + } + + private fun parseLongFromIntent( intent: Intent, longKey: String, stringKey: String ): Long { @@ -167,7 +146,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/io/github/muntashirakon/music/activities/PermissionActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/PermissionActivity.kt new file mode 100644 index 000000000..b448836a1 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/activities/PermissionActivity.kt @@ -0,0 +1,63 @@ +package code.name.monkey.retromusic.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import androidx.core.text.HtmlCompat +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.VersionUtils +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity +import code.name.monkey.retromusic.extensions.accentBackgroundColor +import code.name.monkey.retromusic.extensions.show +import code.name.monkey.retromusic.util.RingtoneManager +import kotlinx.android.synthetic.main.activity_permission.* +import kotlinx.android.synthetic.main.fragment_library.appNameText + + +class PermissionActivity : AbsMusicServiceActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView((R.layout.activity_permission)) + setStatusbarColorAuto() + setNavigationbarColorAuto() + setLightNavigationBar(true) + setTaskDescriptionColorAuto() + setupTitle() + + storagePermission.setButtonClick { + requestPermissions() + } + if (VersionUtils.hasMarshmallow()) audioPermission.show() + audioPermission.setButtonClick { + if (RingtoneManager.requiresDialog(this@PermissionActivity)) { + val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) + intent.data = Uri.parse("package:" + applicationContext.packageName) + startActivity(intent) + } + } + finish.accentBackgroundColor() + finish.setOnClickListener { + if (hasPermissions() && !RingtoneManager.requiresDialog(this)) { + startActivity( + Intent(this, MainActivity::class.java).addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + ) + ) + finish() + } + } + } + + private fun setupTitle() { + val color = ThemeStore.accentColor(this) + val hexColor = String.format("#%06X", 0xFFFFFF and color) + val appName = HtmlCompat.fromHtml( + "Hello there!
Welcome to Retro Music", + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + appNameText.text = appName + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt index ad4661670..40bdff127 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/PlayingQueueActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt index 5ee650c7f..a797a2809 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsBaseActivity.kt @@ -46,7 +46,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() { override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) if (!hasPermissions()) { - requestPermissions() + //requestPermissions() } } diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt index c7da95d2a..9d10348df 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.toPlayCount import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.interfaces.MusicServiceEventListener +import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.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/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt index e05776f72..c06f0a7c4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt @@ -12,11 +12,9 @@ import androidx.core.view.isVisible import androidx.lifecycle.Observer import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil -import com.google.android.material.bottomsheet.BottomSheetBehavior import io.github.muntashirakon.music.R import io.github.muntashirakon.music.RetroBottomSheetBehavior import io.github.muntashirakon.music.extensions.hide -import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.whichFragment import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.MiniPlayerFragment @@ -26,6 +24,7 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.CategoryInfo import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.views.BottomNavigationBarTinted +import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import org.koin.androidx.viewmodel.ext.android.viewModel @@ -34,7 +33,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { 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() @@ -186,6 +178,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { behavior.isHideable = true behavior.peekHeight = 0 collapsePanel() + ViewCompat.setElevation(slidingPanel, 0f) ViewCompat.setElevation(bottomNavigationView, 10f) } else { ViewCompat.setElevation(bottomNavigationView, 10f) diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java index a8d4c50f3..e1f12ddee 100644 --- a/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java +++ b/app/src/main/java/io/github/muntashirakon/music/activities/bugreport/model/DeviceInfo.java @@ -87,7 +87,7 @@ public class DeviceInfo { return "Device info:\n" + "---\n" + "\n" - + "\n" + + "\n" + "\n" + "\n" + "\n" diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt index 67f300d90..7a13acf8d 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AbsTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 @@ -41,7 +40,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { val repository by inject() lateinit var saveFab: MaterialButton - protected var id: Int = 0 + protected var id: Long = 0 private set private var paletteColorPrimary: Int = 0 private var isInNoImageMode: Boolean = false @@ -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() } @@ -254,11 +251,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { private fun getIntentExtras() { val intentExtras = intent.extras if (intentExtras != null) { - id = intentExtras.getInt(EXTRA_ID) + id = intentExtras.getLong(EXTRA_ID) } } - protected abstract suspend fun getSongPaths(): List + protected abstract fun getSongPaths(): List protected fun searchWebFor(vararg keys: String) { val stringBuilder = StringBuilder() @@ -399,7 +396,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } - class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?) + class ArtworkInfo constructor(val albumId: Long, val artwork: Bitmap?) companion object { diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt index 9f01b7063..a4d2c3814 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt @@ -14,17 +14,18 @@ import android.transition.Slide import android.widget.Toast import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.MaterialUtil -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.animation.GlideAnimation -import com.bumptech.glide.request.target.SimpleTarget import io.github.muntashirakon.music.R import io.github.muntashirakon.music.extensions.appHandleColor import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper +import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.ImageUtil import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette import io.github.muntashirakon.music.util.RetroColorUtil.getColor +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.request.animation.GlideAnimation +import com.bumptech.glide.request.target.SimpleTarget import kotlinx.android.synthetic.main.activity_album_tag_editor.* import org.jaudiotagger.tag.FieldKey import java.util.* @@ -44,9 +45,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,13 +168,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { ) } - override suspend fun getSongPaths(): List { - val songs = repository.albumById(id).songs - val paths = ArrayList(songs!!.size) - for (song in songs) { - paths.add(song.data) - } - return paths + override fun getSongPaths(): List { + return repository.albumById(id).songs + .map(Song::data) } override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { diff --git a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt index 30ae4ce48..baf9f580f 100755 --- a/app/src/main/java/io/github/muntashirakon/music/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java b/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java index c31276854..55132839c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/CategoryInfoAdapter.java @@ -71,13 +71,13 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter { - if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) { - categoryInfo.visible = !categoryInfo.visible; - holder.checkBox.setChecked(categoryInfo.visible); + if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) { + categoryInfo.setVisible(!categoryInfo.isVisible()); + holder.checkBox.setChecked(categoryInfo.isVisible()); } else { Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category, Toast.LENGTH_SHORT).show(); @@ -110,9 +110,9 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter, 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/io/github/muntashirakon/music/adapter/HomeAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/HomeAdapter.kt index ac163e1a7..0d9d57955 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/HomeAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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) @@ -226,7 +233,7 @@ class HomeAdapter( fun gridLayoutManager() = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false) fun linearLayoutManager() = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) - override fun onArtist(artistId: Int, imageView: ImageView) { + override fun onArtist(artistId: Long, imageView: ImageView) { activity.findNavController(R.id.fragment_container).navigate( R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId), @@ -237,7 +244,7 @@ class HomeAdapter( ) } - override fun onAlbumClick(albumId: Int, view: View) { + override fun onAlbumClick(albumId: Long, view: View) { activity.findNavController(R.id.fragment_container).navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt index fcef5c254..8726619d8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/SearchAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt index 011356c60..0ad1834e4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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) } @@ -137,7 +136,7 @@ open class AlbumAdapter( private fun getSongList(albums: List): List { val songs = ArrayList() for (album in albums) { - songs.addAll(album.songs!!) + songs.addAll(album.songs) } return songs } @@ -171,7 +170,7 @@ open class AlbumAdapter( if (isInQuickSelectMode) { toggleChecked(layoutPosition) } else { - image?.let { albumClickListener?.onAlbumClick(dataSet[layoutPosition].id, it) } + albumClickListener?.onAlbumClick(dataSet[layoutPosition].id, itemView) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt index 16a6bcba4..64d1ca381 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/album/AlbumCoverPagerAdapter.kt @@ -90,6 +90,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 +98,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/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt index cb6dd36e1..9d53628f5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt @@ -3,8 +3,6 @@ package io.github.muntashirakon.music.adapter.album import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentActivity -import code.name.monkey.appthemehelper.util.ATHUtil -import com.bumptech.glide.Glide import io.github.muntashirakon.music.fragments.albums.AlbumClickListener import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.RetroMusicColoredTarget @@ -13,6 +11,7 @@ import io.github.muntashirakon.music.interfaces.CabHolder import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor +import com.bumptech.glide.Glide class HorizontalAlbumAdapter( activity: FragmentActivity, @@ -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/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java index 86d7982bf..8895d1e7d 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/base/MediaEntryViewHolder.java @@ -113,6 +113,7 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold itemView.setOnLongClickListener(this); } + @Nullable @Override public View getSwipeableContainerView() { return null; @@ -129,11 +130,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold } public void setImageTransitionName(@NonNull String transitionName) { - if (imageContainerCard != null) { + itemView.setTransitionName(transitionName); + /* if (imageContainerCard != null) { imageContainerCard.setTransitionName(transitionName); } if (image != null) { image.setTransitionName(transitionName); - } + }*/ } } diff --git a/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/LegacyPlaylistAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/LegacyPlaylistAdapter.kt new file mode 100644 index 000000000..c74c8a5f9 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt index 223cff33d..70f0ef9af 100755 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.interfaces.CabHolder -import io.github.muntashirakon.music.model.AbsCustomPlaylist import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.model.smartplaylist.AbsSmartPlaylist import io.github.muntashirakon.music.repository.PlaylistSongsLoader import io.github.muntashirakon.music.util.AutoGeneratedPlaylistBitmap import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.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/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt index 0bc4efe30..88ab0eac0 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt @@ -3,19 +3,23 @@ package io.github.muntashirakon.music.adapter.song import android.view.MenuItem 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.db.PlaylistEntity +import code.name.monkey.retromusic.db.toSongEntity +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 +import code.name.monkey.retromusic.util.ViewUtil import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags -import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.R.menu -import io.github.muntashirakon.music.dialogs.RemoveFromPlaylistDialog -import io.github.muntashirakon.music.interfaces.CabHolder -import io.github.muntashirakon.music.model.PlaylistSong -import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.util.ViewUtil class OrderablePlaylistSongAdapter( + private val playlist: PlaylistEntity, activity: FragmentActivity, dataSet: ArrayList, itemLayoutRes: Int, @@ -54,8 +58,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 +122,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/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt index 4f3c1891a..96a939710 100644 --- a/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt @@ -4,15 +4,6 @@ import android.view.MenuItem import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity -import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter -import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange -import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags -import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter -import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants -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 io.github.muntashirakon.music.R import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying @@ -21,6 +12,14 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.ViewUtil +import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter +import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange +import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags +import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter +import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants +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 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/io/github/muntashirakon/music/db/BlackListStoreDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreDao.kt new file mode 100644 index 000000000..db0dd0f64 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/BlackListStoreEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/BlackListStoreEntity.kt new file mode 100644 index 000000000..5ccbce07c --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/HistoryDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/HistoryDao.kt new file mode 100644 index 000000000..97288235d --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long): 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/io/github/muntashirakon/music/db/HistoryEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/HistoryEntity.kt new file mode 100644 index 000000000..a4facd796 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long, + 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: Long, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Long, + @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/io/github/muntashirakon/music/db/LyricsDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/LyricsDao.kt new file mode 100644 index 000000000..a09b430a2 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/LyricsEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/LyricsEntity.kt new file mode 100644 index 000000000..0cec6431c --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/PlayCountDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlayCountDao.kt new file mode 100644 index 000000000..4107b7ba5 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long): List + + @Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC") + fun playCountSongs(): List + + @Query("DELETE FROM SongEntity WHERE id =:songId") + fun deleteSong(songId: Long) + + @Query("UPDATE PlayCountEntity SET play_count = play_count + 1 WHERE id = :id") + fun updateQuantity(id: Long) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/db/PlayCountEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlayCountEntity.kt new file mode 100644 index 000000000..0fe6c0885 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long, + 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: Long, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Long, + @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/io/github/muntashirakon/music/db/PlaylistDao.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistDao.kt new file mode 100644 index 000000000..36a4e8330 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long, 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: Long) + + @Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId") + suspend fun deleteSongFromPlaylist(playlistId: Long, songId: Long) + + @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: Long, songId: Long): List + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId") + fun songsFromPlaylist(playlistId: Long): 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: Long): LiveData> + + @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId") + fun favoritesSongs(playlistId: Long): List + + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/db/PlaylistEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistEntity.kt new file mode 100644 index 000000000..236e9cb40 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistEntity.kt @@ -0,0 +1,17 @@ +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( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "playlist_id") + val playListId: Long = 0, + @ColumnInfo(name = "playlist_name") + val playlistName: String +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/db/PlaylistWithSongs.kt b/app/src/main/java/io/github/muntashirakon/music/db/PlaylistWithSongs.kt new file mode 100644 index 000000000..5a256bde7 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/RetroDatabase.kt b/app/src/main/java/io/github/muntashirakon/music/db/RetroDatabase.kt new file mode 100644 index 000000000..6be545b6a --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/db/SongEntity.kt b/app/src/main/java/io/github/muntashirakon/music/db/SongEntity.kt new file mode 100644 index 000000000..6a6d236a3 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/db/SongEntity.kt @@ -0,0 +1,39 @@ +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( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "song_key") + val songPrimaryKey: Long = 0L, + @ColumnInfo(name = "playlist_creator_id") + val playlistCreatorId: Long, + val id: Long, + 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: Long, + @ColumnInfo(name = "album_name") + val albumName: String, + @ColumnInfo(name = "artist_id") + val artistId: Long, + @ColumnInfo(name = "artist_name") + val artistName: String, + val composer: String?, + @ColumnInfo(name = "album_artist") + val albumArtist: String? +) : Parcelable + diff --git a/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt b/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt new file mode 100644 index 000000000..03666f0a2 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/db/SongExtension.kt @@ -0,0 +1,133 @@ +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: Long): List { + return map { + it.toSongEntity(playlistId) + } +} + +fun Song.toHistoryEntity(timePlayed: Long): HistoryEntity { + return HistoryEntity( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist, + timePlayed = timePlayed + ) +} + +fun Song.toSongEntity(playListId: Long): SongEntity { + return SongEntity( + playlistCreatorId = playListId, + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist + ) +} + +fun SongEntity.toSong(): Song { + return Song( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist + ) +} + +fun PlayCountEntity.toSong(): Song { + return Song( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist + ) +} + +fun HistoryEntity.toSong(): Song { + return Song( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist + ) +} + +fun Song.toPlayCount(): PlayCountEntity { + return PlayCountEntity( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist, + timePlayed = System.currentTimeMillis(), + playCount = 1 + ) +} + +fun List.toSongsEntity(playlistEntity: PlaylistEntity): List { + return map { + it.toSongEntity(playlistEntity.playListId) + } +} diff --git a/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt index 79560c438..b76d4b186 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt @@ -1,76 +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 io.github.muntashirakon.music.dialogs import android.app.Dialog import android.os.Bundle +import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope +import io.github.muntashirakon.music.EXTRA_PLAYLISTS import io.github.muntashirakon.music.EXTRA_SONG import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSongsEntity import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.materialDialog +import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.fragments.ReloadType.Playlists import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.repository.PlaylistRepository -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt index 088fe7d38..35787a8ab 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt @@ -1,88 +1,75 @@ -/* - * 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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.EXTRA_SONG import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.extensions.colorButtons -import io.github.muntashirakon.music.extensions.extraNotNull +import io.github.muntashirakon.music.extensions.extra import io.github.muntashirakon.music.extensions.materialDialog +import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.fragments.ReloadType.Playlists import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.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 = playlistName)) + libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId) }) + 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/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt index 6f416a585..8d4359788 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.PlaylistEntity import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.materialDialog -import io.github.muntashirakon.music.model.Playlist -import io.github.muntashirakon.music.util.PlaylistsUtil +import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/DeleteSongsAsyncTask.java b/app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsAsyncTask.java deleted file mode 100644 index c038d0aa1..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.R; -import io.github.muntashirakon.music.activities.saf.SAFGuideActivity; -import io.github.muntashirakon.music.misc.DialogAsyncTask; -import io.github.muntashirakon.music.model.Song; -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt index 43ba88dc5..ae441e4c4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.EXTRA_SONG import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.activities.saf.SAFGuideActivity import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.materialDialog +import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.MusicUtil -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/ImportPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/ImportPlaylistDialog.kt new file mode 100644 index 000000000..359ef1c5b --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/dialogs/LyricsDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/LyricsDialog.kt new file mode 100644 index 000000000..9c3b63c78 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/dialogs/RemoveFromPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveFromPlaylistDialog.kt deleted file mode 100644 index 8c0a3d352..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveFromPlaylistDialog.kt +++ /dev/null @@ -1,83 +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 io.github.muntashirakon.music.dialogs - -import android.app.Dialog -import android.os.Bundle -import androidx.core.text.HtmlCompat -import androidx.fragment.app.DialogFragment -import io.github.muntashirakon.music.EXTRA_SONG -import io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.colorButtons -import io.github.muntashirakon.music.extensions.materialDialog -import io.github.muntashirakon.music.model.PlaylistSong -import io.github.muntashirakon.music.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(R.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(R.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/io/github/muntashirakon/music/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveSongFromPlaylistDialog.kt new file mode 100644 index 000000000..b52e9fef5 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt index 466f08916..ea6738145 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.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 io.github.muntashirakon.music.EXTRA_PLAYLIST_ID import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.extensions.accentColor import io.github.muntashirakon.music.extensions.colorButtons import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.materialDialog -import io.github.muntashirakon.music.util.PlaylistsUtil +import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/RetroSingleCheckedListAdapter.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/RetroSingleCheckedListAdapter.kt deleted file mode 100644 index 151961214..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/RetroSingleCheckedListAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.muntashirakon.music.dialogs - -import android.content.Context -import android.widget.ArrayAdapter -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SavePlaylistDialog.kt new file mode 100644 index 000000000..dd92a9a2d --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt index e5f0d6039..fc88b8489 100755 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt b/app/src/main/java/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt index e03540c8a..a6235e8d1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/dialogs/SongDetailDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/extensions/ActivityEx.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt index c525ef166..e4a1dacb9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt +++ b/app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt @@ -15,13 +15,8 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.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/io/github/muntashirakon/music/extensions/ColorExt.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/ColorExt.kt index 3f93d4d3c..bf069359a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/ColorExt.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 @@ -31,6 +37,8 @@ import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper 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 import io.github.muntashirakon.music.App @@ -41,6 +49,7 @@ fun Int.ripAlpha(): Int { } fun Dialog.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal) + fun Toolbar.backgroundTintList() { val surfaceColor = ATHUtil.resolveColor(context, R.attr.colorSurface, Color.BLACK) val colorStateList = ColorStateList.valueOf(surfaceColor) @@ -76,7 +85,6 @@ fun Fragment.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) = fun Dialog.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) = ATHUtil.resolveColor(context, attr, fallBackColor) - fun CheckBox.addAccentColor() { buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context)) } @@ -91,6 +99,23 @@ fun Button.accentTextColor() { setTextColor(ThemeStore.accentColor(App.getContext())) } +fun MaterialButton.accentTextColor() { + setTextColor(ThemeStore.accentColor(App.getContext())) +} + +fun MaterialButton.accentBackgroundColor() { + backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(App.getContext())) +} + +fun MaterialButton.accentOutlineColor() { + val color = ThemeStore.accentColor(context) + val colorStateList = ColorStateList.valueOf(color) + iconTint = colorStateList + strokeColor = colorStateList + setTextColor(colorStateList) + rippleColor = colorStateList +} + fun SeekBar.applyColor(@ColorInt color: Int) { thumbTintList = ColorStateList.valueOf(color) progressTintList = ColorStateList.valueOf(color) @@ -107,6 +132,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 +154,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 +168,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/io/github/muntashirakon/music/extensions/CursorExtensions.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/CursorExtensions.kt new file mode 100644 index 000000000..22c8e8667 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/extensions/CursorExtensions.kt @@ -0,0 +1,37 @@ +package code.name.monkey.retromusic.extensions + +import android.database.Cursor + +// exception is rethrown manually in order to have a readable stacktrace + +internal fun Cursor.getInt(columnName: String): Int { + try { + return this.getInt(this.getColumnIndex(columnName)) + } catch (ex: Throwable) { + throw IllegalStateException("invalid column $columnName", ex) + } +} + +internal fun Cursor.getLong(columnName: String): Long { + try { + return this.getLong(this.getColumnIndex(columnName)) + } catch (ex: Throwable) { + throw IllegalStateException("invalid column $columnName", ex) + } +} + +internal fun Cursor.getString(columnName: String): String { + try { + return this.getString(this.getColumnIndex(columnName)) + } catch (ex: Throwable) { + throw IllegalStateException("invalid column $columnName", ex) + } +} + +internal fun Cursor.getStringOrNull(columnName: String): String? { + try { + return this.getString(this.getColumnIndex(columnName)) + } catch (ex: Throwable) { + throw IllegalStateException("invalid column $columnName", ex) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/extensions/DialogExtension.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/DialogExtension.kt index fd375c107..5f1252ca5 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/DialogExtension.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/extensions/FragmentExt.kt b/app/src/main/java/io/github/muntashirakon/music/extensions/FragmentExt.kt index 7c300f54a..d9244bde0 100644 --- a/app/src/main/java/io/github/muntashirakon/music/extensions/FragmentExt.kt +++ b/app/src/main/java/io/github/muntashirakon/music/extensions/FragmentExt.kt @@ -2,12 +2,15 @@ package io.github.muntashirakon.music.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 @@ -73,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/io/github/muntashirakon/music/fragments/CoroutineViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/CoroutineViewModel.kt new file mode 100644 index 000000000..9b6a5ca4c --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/fragments/DetailListFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt index f90145eca..9aeda938b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt @@ -3,6 +3,11 @@ package io.github.muntashirakon.music.fragments import android.os.Bundle import android.view.View import android.widget.ImageView +import androidx.core.os.bundleOf +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.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager @@ -10,15 +15,14 @@ import io.github.muntashirakon.music.* import io.github.muntashirakon.music.adapter.album.AlbumAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter +import io.github.muntashirakon.music.db.toSong import io.github.muntashirakon.music.fragments.albums.AlbumClickListener import io.github.muntashirakon.music.fragments.artists.ArtistClickListener import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist -import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.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 +38,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 +52,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 +147,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) { @@ -120,11 +181,21 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de GridLayoutManager(requireContext(), 2, GridLayoutManager.VERTICAL, false) - override fun onArtist(artistId: Int, imageView: ImageView) { - + override fun onArtist(artistId: Long, imageView: ImageView) { + findNavController().navigate( + R.id.artistDetailsFragment, + bundleOf(EXTRA_ARTIST_ID to artistId), + null, + FragmentNavigatorExtras(imageView to getString(R.string.transition_artist_image)) + ) } - override fun onAlbumClick(albumId: Int, view: View) { - + override fun onAlbumClick(albumId: Long, view: View) { + findNavController().navigate( + R.id.albumDetailsFragment, + bundleOf(EXTRA_ALBUM_ID to albumId), + null, + FragmentNavigatorExtras(view to getString(R.string.transition_album_art)) + ) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt index 6406d0ffc..d1848bd15 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt @@ -4,83 +4,130 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.fragments.ReloadType.* +import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.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 +136,10 @@ class LibraryViewModel( } override fun onMediaStoreChanged() { - loadLibraryContent() println("onMediaStoreChanged") + loadLibraryContent() } - override fun onServiceConnected() { println("onServiceConnected") } @@ -108,6 +154,7 @@ class LibraryViewModel( override fun onPlayingMetaChanged() { println("onPlayingMetaChanged") + } override fun onPlayStateChanged() { @@ -122,11 +169,76 @@ class LibraryViewModel( println("onShuffleModeChanged") } + fun shuffleSongs() = viewModelScope.launch(IO) { + val songs = repository.allSongs() + MusicPlayerRemote.openAndShuffleQueue( + songs, + true + ) + } + + fun renameRoomPlaylist(playListId: Long, 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: Long) = repository.albumById(id) + suspend fun artistById(id: Long) = 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(playlistName = playlist.name)) + val songEntities = playlist.getSongs().map { + it.toSongEntity(playListId) + } + 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/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt index 69636ffba..adaa793f8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R -import io.github.muntashirakon.music.extensions.show -import io.github.muntashirakon.music.extensions.textColorPrimary -import io.github.muntashirakon.music.extensions.textColorSecondary +import io.github.muntashirakon.music.extensions.* import io.github.muntashirakon.music.fragments.base.AbsMusicServiceFragment import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt index 964127b53..79972c57b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/NowPlayingScreen.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt index 15bf1b0e9..b61af2bbb 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt @@ -6,8 +6,11 @@ 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.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator @@ -15,7 +18,6 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper -import com.bumptech.glide.Glide import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.R @@ -26,22 +28,33 @@ import io.github.muntashirakon.music.adapter.song.SimpleSongAdapter import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.DeleteSongsDialog import io.github.muntashirakon.music.extensions.applyColor +import io.github.muntashirakon.music.extensions.applyOutlineColor import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.glide.RetroMusicColoredTarget +import io.github.muntashirakon.music.glide.SingleColorTarget import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist +import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmAlbum +import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor +import com.bumptech.glide.Glide +import com.google.android.material.transition.platform.MaterialArcMotion +import com.google.android.material.transition.platform.MaterialContainerTransform 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.* @@ -60,29 +73,27 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det private val savedSortOrder: String get() = PreferenceUtil.albumDetailSongSortOrder - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + sharedElementEnterTransition = MaterialContainerTransform().apply { + duration = 1000L + pathMotion = MaterialArcMotion() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) 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) @@ -91,11 +102,11 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det bundleOf(EXTRA_ARTIST_ID to album.artistId) ) } - playAction.setOnClickListener { MusicPlayerRemote.openQueue(album.songs!!, 0, true) } + playAction.setOnClickListener { MusicPlayerRemote.openQueue(album.songs, 0, true) } shuffleAction.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue( - album.songs!!, + album.songs, true ) } @@ -134,20 +145,18 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det } private fun showAlbum(album: Album) { - if (album.songs!!.isEmpty()) { + if (album.songs.isEmpty()) { return } 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 +171,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 +215,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 +233,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,30 +248,29 @@ 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) { + override fun onAlbumClick(albumId: Long, 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)) ) } @@ -275,7 +305,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 -> { @@ -326,29 +362,31 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det private fun setSaveSortOrder(sortOrder: String) { PreferenceUtil.albumDetailSongSortOrder = sortOrder - when (sortOrder) { - SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 -> + val songs = when (sortOrder) { + SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs.sortedWith { o1, o2 -> o1.trackNumber.compareTo( o2.trackNumber ) - }) - SortOrder.AlbumSongSortOrder.SONG_A_Z -> album.songs?.sortWith(Comparator { o1, o2 -> + } + SortOrder.AlbumSongSortOrder.SONG_A_Z -> album.songs.sortedWith { o1, o2 -> o1.title.compareTo( o2.title ) - }) - SortOrder.AlbumSongSortOrder.SONG_Z_A -> album.songs?.sortWith(Comparator { o1, o2 -> + } + SortOrder.AlbumSongSortOrder.SONG_Z_A -> album.songs.sortedWith { o1, o2 -> o2.title.compareTo( o1.title ) - }) - SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs?.sortWith(Comparator { o1, o2 -> + } + SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs.sortedWith { o1, o2 -> o1.duration.compareTo( o2.duration ) - }) + } + else -> throw IllegalArgumentException("invalid $sortOrder") } - album.songs?.let { simpleSongAdapter.swapDataSet(it) } + album = album.copy(songs = songs) + simpleSongAdapter.swapDataSet(album.songs) } companion object { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt index 65b7338b2..e50d7f69c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt @@ -1,68 +1,43 @@ package io.github.muntashirakon.music.fragments.albums import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.liveData import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Artist +import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmAlbum import io.github.muntashirakon.music.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 + private val repository: RealRepository, + private val albumId: Long ) : 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) { + emit(repository.albumByIdAsync(albumId)) } - private fun loadAlbumDetails() = viewModelScope.launch { - val album = loadAlbumAsync.await() ?: throw NullPointerException("Album couldn't found") - _album.postValue(album) + fun getArtist(artistId: Long): LiveData = liveData(IO) { + val artist = repository.artistById(artistId) + emit(artist) } - fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) { - try { - val lastFmAlbum = realRepository.albumInfo( - album.artistName ?: "-", album.title ?: "-" - ) - _lastFmAlbum.postValue(lastFmAlbum) - } catch (ignored: Exception) {} + fun getAlbumInfo(album: Album): LiveData> = liveData { + emit(Result.Loading) + emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-")) } - fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) { - val artist = realRepository.artistById(artistId) - _artist.postValue(artist) - - artist.albums?.filter { item -> item.id != albumId }?.let { albums -> - if (albums.isNotEmpty()) _moreAlbums.postValue(albums) + fun getMoreAlbums(artist: Artist): LiveData> = liveData(IO) { + artist.albums.filter { item -> item.id != albumId }.let { albums -> + if (albums.isNotEmpty()) emit(albums) } } - private val loadAlbumAsync: Deferred - get() = viewModelScope.async(Dispatchers.IO) { - realRepository.albumById(albumId) - } - override fun onMediaStoreChanged() { - loadAlbumDetails() + } override fun onServiceConnected() {} diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt index 9724b3247..b4492d089 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt @@ -1,26 +1,35 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.album.AlbumAdapter +import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment +import io.github.muntashirakon.music.helper.SortOrder +import io.github.muntashirakon.music.helper.SortOrder.AlbumSortOrder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroUtil +import com.google.android.material.transition.platform.MaterialFadeThrough -class AlbumsFragment : - AbsRecyclerViewCustomGridSizeFragment(), + +class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment(), AlbumClickListener { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() + } + 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 @@ -40,7 +49,7 @@ class AlbumsFragment : return AlbumAdapter( requireActivity(), dataSet, - R.layout.item_grid, + itemLayoutRes(), null, this ) @@ -94,9 +103,8 @@ class AlbumsFragment : } } - override fun onAlbumClick(albumId: Int, view: View) { - val controller = requireActivity().findNavController(R.id.fragment_container) - controller.navigate( + override fun onAlbumClick(albumId: Long, view: View) { + findActivityNavController(R.id.fragment_container).navigate( R.id.albumDetailsFragment, bundleOf(EXTRA_ALBUM_ID to albumId), null, @@ -105,8 +113,183 @@ 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 { - fun onAlbumClick(albumId: Int, view: View) + fun onAlbumClick(albumId: Long, view: View) } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsFragment.kt index 679be368a..e8b25d765 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsFragment.kt @@ -10,33 +10,41 @@ 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 com.bumptech.glide.Glide +import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.album.HorizontalAlbumAdapter import io.github.muntashirakon.music.adapter.song.SimpleSongAdapter import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.extensions.applyColor +import io.github.muntashirakon.music.extensions.applyOutlineColor import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.showToast import io.github.muntashirakon.music.fragments.albums.AlbumClickListener import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.glide.ArtistGlideRequest -import io.github.muntashirakon.music.glide.RetroMusicColoredTarget +import io.github.muntashirakon.music.glide.SingleColorTarget import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.Artist +import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmArtist +import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.util.CustomArtistImageUtil import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.RetroUtil -import io.github.muntashirakon.music.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 +74,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 +138,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 +147,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 +188,24 @@ 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) { + shuffleAction.applyColor(color) + playAction.applyOutlineColor(color) } - override fun onAlbumClick(albumId: Int, view: View) { + + override fun onAlbumClick(albumId: Long, 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 +230,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/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt index 06a6d15e2..0bdd3b140 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt @@ -1,51 +1,37 @@ package io.github.muntashirakon.music.fragments.artists import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.liveData import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.model.Artist +import io.github.muntashirakon.music.network.Result import io.github.muntashirakon.music.network.model.LastFmArtist import io.github.muntashirakon.music.repository.RealRepository -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers.IO class ArtistDetailsViewModel( private val realRepository: RealRepository, - private val artistId: Int + private val artistId: Long ) : 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/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt index bb641ff98..7cc396b54 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt @@ -1,7 +1,7 @@ package io.github.muntashirakon.music.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,22 @@ import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment -import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks +import io.github.muntashirakon.music.helper.SortOrder.ArtistSortOrder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroUtil +import com.google.android.material.transition.platform.MaterialFadeThrough -class ArtistsFragment : - AbsRecyclerViewCustomGridSizeFragment(), - MainActivityFragmentCallbacks, ArtistClickListener { - override fun handleBackPress(): Boolean { - return false +class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment(), + ArtistClickListener { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() } - 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 @@ -50,7 +51,7 @@ class ArtistsFragment : return ArtistAdapter( requireActivity(), dataSet, - R.layout.item_grid_circle, + itemLayoutRes(), null, this ) @@ -100,12 +101,167 @@ class ArtistsFragment : } } - override fun onArtist(artistId: Int, imageView: ImageView) { + override fun onArtist(artistId: Long, imageView: ImageView) { 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 { - fun onArtist(artistId: Int, imageView: ImageView) + fun onArtist(artistId: Long, imageView: ImageView) } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsPlayerFragment.kt index 5e3cc98d7..52a32e945 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsPlayerFragment.kt @@ -1,10 +1,9 @@ package io.github.muntashirakon.music.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,41 @@ 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 io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.R import io.github.muntashirakon.music.activities.tageditor.AbsTagEditorActivity import io.github.muntashirakon.music.activities.tageditor.SongTagEditorActivity +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.dialogs.* import io.github.muntashirakon.music.extensions.hide +import io.github.muntashirakon.music.extensions.whichFragment import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.player.PlayerAlbumCoverFragment import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.interfaces.PaletteColorHolder import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.lyrics.Lyrics +import io.github.muntashirakon.music.repository.RealRepository +import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.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 +74,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 +162,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 +183,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 +259,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/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt index 8cc11fdbe..ba9270647 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsRecyclerViewFragment.kt @@ -78,8 +78,7 @@ abstract class AbsRecyclerViewFragment, LM : Recycle return String(Character.toChars(unicode)) } - private fun checkIsEmpty() { - emptyEmoji.text = getEmojiByUnicode(0x1F631) + private fun checkIsEmpty() { emptyText.setText(emptyMessage) empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE } diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt index 5b676af48..99febc1e6 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenreDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt index a4f623639..ad4c8a37b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt @@ -21,18 +21,17 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.GenreAdapter import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment -import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks +import com.google.android.material.transition.platform.MaterialFadeThrough -class GenresFragment : AbsRecyclerViewFragment(), - MainActivityFragmentCallbacks { - - override fun handleBackPress(): Boolean { - return false +class GenresFragment : AbsRecyclerViewFragment() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() } 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/io/github/muntashirakon/music/fragments/home/HomeFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/home/HomeFragment.kt index b68533c2d..1e43ba0ee 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/home/HomeFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/home/HomeFragment.kt @@ -16,50 +16,41 @@ package io.github.muntashirakon.music.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 com.bumptech.glide.Glide -import io.github.muntashirakon.music.EXTRA_PLAYLIST +import io.github.muntashirakon.music.HISTORY_PLAYLIST +import io.github.muntashirakon.music.LAST_ADDED_PLAYLIST import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.TOP_PLAYED_PLAYLIST import io.github.muntashirakon.music.adapter.HomeAdapter import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.glide.ProfileBannerGlideRequest import io.github.muntashirakon.music.glide.UserProfileGlideRequest -import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.model.smartplaylist.HistoryPlaylist -import io.github.muntashirakon.music.model.smartplaylist.LastAddedPlaylist -import io.github.muntashirakon.music.model.smartplaylist.TopTracksPlaylist -import io.github.muntashirakon.music.repository.Repository import io.github.muntashirakon.music.util.NavigationUtil import io.github.muntashirakon.music.util.PreferenceUtil +import com.bumptech.glide.Glide +import com.google.android.material.transition.platform.MaterialFadeThrough 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) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() + } - 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 +65,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 +104,7 @@ class HomeFragment : adapter = homeAdapter } - libraryViewModel.homeLiveData.observe(viewLifecycleOwner, Observer { + libraryViewModel.getHome().observe(viewLifecycleOwner, Observer { homeAdapter.swapData(it) }) @@ -138,6 +124,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/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt index 7243bb12f..6a9bf6306 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 com.google.android.material.appbar.AppBarLayout import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.dialogs.CreatePlaylistDialog +import io.github.muntashirakon.music.dialogs.ImportPlaylistDialog import io.github.muntashirakon.music.extensions.findNavController import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment 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/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt index 3eb89e3e4..6721be988 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt @@ -20,7 +20,6 @@ import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import io.github.muntashirakon.music.R import io.github.muntashirakon.music.RetroBottomSheetBehavior -import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt index 7d75b09db..83a3781b1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt @@ -3,9 +3,7 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.misc.SimpleOnSeekbarChangeListener import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.util.MusicUtil - import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import kotlinx.android.synthetic.main.fragment_fit_playback_controls.* diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt index af21957ff..31f068ce1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 @@ -15,14 +14,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.ViewCompat +import androidx.lifecycle.lifecycleScope 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 io.github.muntashirakon.music.R import io.github.muntashirakon.music.RetroBottomSheetBehavior -import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter +import io.github.muntashirakon.music.db.PlaylistEntity +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.db.toSongEntity import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.ripAlpha import io.github.muntashirakon.music.extensions.show @@ -39,7 +40,7 @@ import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.ViewUtil import io.github.muntashirakon.music.util.color.MediaNotificationProcessor -import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.* import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager @@ -48,6 +49,9 @@ import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import kotlinx.android.synthetic.main.fragment_gradient_controls.* import kotlinx.android.synthetic.main.fragment_gradient_player.* import kotlinx.android.synthetic.main.status_bar.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_player), MusicProgressViewUpdateHelper.Callback, @@ -62,14 +66,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() { + private val bottomSheetCallbackList = object : 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 +80,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) + STATE_EXPANDED, + STATE_DRAGGING -> { + mainActivity.getBottomSheetBehavior().setAllowDragging(false) } - BottomSheetBehavior.STATE_COLLAPSED -> { + STATE_COLLAPSED -> { resetToCurrentPosition() - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } else -> { - activity.getBottomSheetBehavior().setAllowDragging(true) + mainActivity.getBottomSheetBehavior().setAllowDragging(true) } } } @@ -139,8 +139,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 +158,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play recyclerViewDragDropManager?.cancelDrag() super.onPause() progressViewUpdateHelper.stop() - } override fun playerToolbar(): Toolbar? { @@ -176,9 +174,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun onBackPressed(): Boolean { var wasExpanded = false - if (getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED) { - wasExpanded = getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED - getQueuePanel().state = BottomSheetBehavior.STATE_COLLAPSED + if (getQueuePanel().state == STATE_EXPANDED) { + wasExpanded = getQueuePanel().state == STATE_EXPANDED + getQueuePanel().state = STATE_COLLAPSED return wasExpanded } return wasExpanded @@ -224,9 +222,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() + updateIsFavoriteIcon() } } @@ -234,6 +231,23 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play toggleFavorite(MusicPlayerRemote.currentSong) } + private fun updateIsFavoriteIcon() { + lifecycleScope.launch(Dispatchers.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(Dispatchers.Main) { + val icon = + if (isFavorite) R.drawable.ic_favorite + else R.drawable.ic_favorite_border + songFavourite.setImageResource(icon) + } + } + } + } + private fun hideVolumeIfAvailable() { if (PreferenceUtil.isVolumeVisibilityMode) { childFragmentManager.beginTransaction() @@ -251,6 +265,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play updatePlayPauseDrawableState() updatePlayPauseDrawableState() updateQueue() + updateIsFavoriteIcon() } override fun onPlayStateChanged() { @@ -269,11 +284,13 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play super.onPlayingMetaChanged() updateSong() updateQueuePosition() + updateIsFavoriteIcon() } override fun onQueueChanged() { super.onQueueChanged() updateLabel() + playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue) } private fun updateSong() { @@ -368,13 +385,10 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play private fun updateLabel() { (MusicPlayerRemote.playingQueue.size - 1).apply { if (this == (MusicPlayerRemote.position)) { - nextSong.hide() + nextSong.text = "Last song" } else { val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title - nextSong.apply { - text = "Next: $title" - show() - } + nextSong.text = title } } } @@ -469,45 +483,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play override fun onUpdateProgressViews(progress: Int, total: Int) { progressSlider.max = total - val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() - 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/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt index 26b27d5bf..08f1a2318 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt @@ -5,23 +5,22 @@ 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 com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator -import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager -import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.extensions.dipToPix import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper -import io.github.muntashirakon.music.model.AbsCustomPlaylist -import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.PlaylistsUtil +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import kotlinx.android.synthetic.main.fragment_playlist_detail.* import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -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,22 @@ 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( + recyclerViewDragDropManager = RecyclerViewDragDropManager() + val animator = RefactoredDefaultItemAnimator() + adapter = + OrderablePlaylistSongAdapter( + playlist.playlistEntity, requireActivity(), ArrayList(), R.layout.item_list, @@ -76,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 ) @@ -87,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() @@ -104,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) } @@ -121,15 +114,10 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli private fun checkIsEmpty() { checkForPadding() - emptyEmoji.text = getEmojiByUnicode(0x1F631) empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE } - private fun getEmojiByUnicode(unicode: Int): String { - return String(Character.toChars(unicode)) - } - override fun onPause() { if (recyclerViewDragDropManager != null) { recyclerViewDragDropManager!!.cancelDrag() @@ -161,11 +149,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/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt index 253208a78..56a98ae2a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt @@ -3,42 +3,29 @@ package io.github.muntashirakon.music.fragments.playlists import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import io.github.muntashirakon.music.App +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.SongEntity import io.github.muntashirakon.music.interfaces.MusicServiceEventListener -import io.github.muntashirakon.music.model.AbsCustomPlaylist -import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.RealRepository -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt index 10bc589d6..18d119a78 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt @@ -1,25 +1,28 @@ package io.github.muntashirakon.music.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 io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.playlist.PlaylistAdapter import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment -import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks +import com.google.android.material.transition.platform.MaterialFadeThrough +import kotlinx.android.synthetic.main.fragment_library.* -class PlaylistsFragment : - AbsRecyclerViewFragment(), - MainActivityFragmentCallbacks { - - override fun handleBackPress(): Boolean { - return false +class PlaylistsFragment : AbsRecyclerViewFragment() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() } 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 +33,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 +46,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/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt index 1a27597b0..ae9538cb1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt @@ -22,7 +22,6 @@ import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt index fdf8a287a..0ec0b5997 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/search/SearchViewModel.kt @@ -6,9 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.github.muntashirakon.music.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.postValue(result) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt index 603773f90..3e8cb1c74 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt @@ -14,13 +14,19 @@ package io.github.muntashirakon.music.fragments.settings +import android.content.res.ColorStateList import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import code.name.monkey.appthemehelper.ThemeStore +import io.github.muntashirakon.music.App import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.extensions.hide +import io.github.muntashirakon.music.extensions.show +import io.github.muntashirakon.music.util.NavigationUtil import kotlinx.android.synthetic.main.fragment_main_settings.* class MainSettingsFragment : Fragment(), View.OnClickListener { diff --git a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt index 78903150c..417457c54 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R +import io.github.muntashirakon.music.fragments.LibraryViewModel +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt b/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt index f33ca639f..351fb9add 100644 --- a/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt +++ b/app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt @@ -1,7 +1,7 @@ package io.github.muntashirakon.music.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,21 @@ import io.github.muntashirakon.music.R import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment -import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks +import io.github.muntashirakon.music.helper.SortOrder.SongSortOrder import io.github.muntashirakon.music.util.PreferenceUtil +import io.github.muntashirakon.music.util.RetroUtil +import com.google.android.material.transition.platform.MaterialFadeThrough -class SongsFragment : - AbsRecyclerViewCustomGridSizeFragment(), - MainActivityFragmentCallbacks { - override fun handleBackPress(): Boolean { - return false +class SongsFragment : AbsRecyclerViewCustomGridSizeFragment() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialFadeThrough() } 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 +43,7 @@ class SongsFragment : return SongAdapter( requireActivity(), dataSet, - R.layout.item_list, + itemLayoutRes(), null ) } @@ -88,6 +89,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/io/github/muntashirakon/music/glide/AlbumGlideRequest.java b/app/src/main/java/io/github/muntashirakon/music/glide/AlbumGlideRequest.java index 1c3c04336..7cb689e68 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/AlbumGlideRequest.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/glide/SingleColorTarget.kt b/app/src/main/java/io/github/muntashirakon/music/glide/SingleColorTarget.kt new file mode 100644 index 000000000..d4b2db1cc --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt index 090cc217e..2b31c6f18 100644 --- a/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt @@ -15,8 +15,8 @@ package io.github.muntashirakon.music.glide.artistimage import android.content.Context -import io.github.muntashirakon.music.deezer.DeezerApiService -import io.github.muntashirakon.music.deezer.Data +import io.github.muntashirakon.music.model.Data +import io.github.muntashirakon.music.network.DeezerService import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt index bcff7bd49..63f03b084 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/HorizontalAdapterHelper.kt @@ -21,7 +21,7 @@ import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/M3UWriter.kt b/app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt index 3f79fc866..7541e4867 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt @@ -13,8 +13,10 @@ */ package io.github.muntashirakon.music.helper -import android.content.Context +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.model.Playlist +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/SearchQueryHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt index 0ccef5cf7..677bc7824 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/SearchQueryHelper.kt @@ -15,7 +15,6 @@ package io.github.muntashirakon.music.helper import android.app.SearchManager -import android.content.Context import android.os.Bundle import android.provider.MediaStore import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt index fc3b94708..f6dab2766 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt @@ -22,7 +22,13 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.repository.GenreRepository +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt index 5afc4541e..0ab167636 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt @@ -15,95 +15,70 @@ package io.github.muntashirakon.music.helper.menu -import android.app.Activity -import android.content.Context import android.view.MenuItem -import android.widget.Toast import androidx.fragment.app.FragmentActivity -import io.github.muntashirakon.music.App import io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.PlaylistWithSongs +import io.github.muntashirakon.music.db.toSongs import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.DeletePlaylistDialog import io.github.muntashirakon.music.dialogs.RenamePlaylistDialog +import io.github.muntashirakon.music.dialogs.SavePlaylistDialog import io.github.muntashirakon.music.helper.MusicPlayerRemote -import io.github.muntashirakon.music.misc.WeakContextAsyncTask -import io.github.muntashirakon.music.model.AbsCustomPlaylist -import io.github.muntashirakon.music.model.Playlist -import io.github.muntashirakon.music.model.Song -import io.github.muntashirakon.music.util.PlaylistsUtil +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt index 67834a943..6f92178b9 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt @@ -32,10 +32,17 @@ import io.github.muntashirakon.music.dialogs.SongDetailDialog import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.interfaces.PaletteColorHolder import io.github.muntashirakon.music.model.Song +import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.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/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt index ad3821f74..7ecd89a21 100644 --- a/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt +++ b/app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt @@ -20,9 +20,15 @@ import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.DeleteSongsDialog import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.model.Song +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/lyrics/Lrc.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java index 33b1811e0..b460b3260 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/Lrc.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/lyrics/LrcEntry.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcEntry.java index 2d29da0ee..8e742a304 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcEntry.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/lyrics/LrcUtils.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcUtils.java index 4959591a6..56704b8e2 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/lyrics/LrcView.java b/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcView.java index e065d2f62..327eb1df1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/lyrics/LrcView.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/model/AbsCustomPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/AbsCustomPlaylist.kt index 9db83c837..5877de018 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/AbsCustomPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/AbsCustomPlaylist.kt @@ -7,8 +7,8 @@ import org.koin.core.KoinComponent import org.koin.core.inject abstract class AbsCustomPlaylist( - id: Int = -1, - name: String = "" + id: Long, + name: String ) : Playlist(id, name), KoinComponent { abstract fun songs(): List diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Album.kt b/app/src/main/java/io/github/muntashirakon/music/model/Album.kt index 0f83eb21b..8fd2086da 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Album.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Album.kt @@ -14,19 +14,15 @@ package io.github.muntashirakon.music.model -import java.util.* - -class Album { - - val songs: ArrayList? - - val id: Int - get() = safeGetFirstSong().albumId +data class Album( + val id: Long, + val songs: List +) { val title: String? get() = safeGetFirstSong().albumName - val artistId: Int + val artistId: Long get() = safeGetFirstSong().artistId val artistName: String? @@ -39,20 +35,17 @@ class Album { get() = safeGetFirstSong().dateModified val songCount: Int - get() = songs!!.size + get() = songs.size val albumArtist: String? get() = safeGetFirstSong().albumArtist - constructor(songs: ArrayList) { - this.songs = songs - } - - constructor() { - this.songs = ArrayList() - } - fun safeGetFirstSong(): Song { - return if (songs!!.isEmpty()) Song.emptySong else songs[0] + return songs.firstOrNull() ?: Song.emptySong } + + companion object { + val empty = Album(-1, emptyList()) + } + } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt b/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt index f78f60deb..4b951202b 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Artist.kt @@ -17,11 +17,10 @@ package io.github.muntashirakon.music.model import io.github.muntashirakon.music.util.MusicUtil import java.util.* -class Artist { - val albums: ArrayList? - - val id: Int - get() = safeGetFirstAlbum().artistId +data class Artist( + val id: Long, + val albums: List +) { val name: String get() { @@ -34,37 +33,25 @@ class Artist { val songCount: Int get() { var songCount = 0 - for (album in albums!!) { + for (album in albums) { songCount += album.songCount } return songCount } val albumCount: Int - get() = albums!!.size + get() = albums.size - val songs: ArrayList - get() { - val songs = ArrayList() - for (album in albums!!) { - songs.addAll(album.songs!!) - } - return songs - } - - constructor(albums: ArrayList) { - this.albums = albums - } - - constructor() { - this.albums = ArrayList() - } + val songs: List + get() = albums.flatMap { it.songs } fun safeGetFirstAlbum(): Album { - return if (albums!!.isEmpty()) Album() else albums[0] + return albums.firstOrNull() ?: Album.empty } companion object { const val UNKNOWN_ARTIST_DISPLAY_NAME = "Unknown Artist" + val empty = Artist(-1, emptyList()) + } } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.java b/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.java deleted file mode 100644 index 76e39dba0..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.java +++ /dev/null @@ -1,83 +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 io.github.muntashirakon.music.model; - -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.DrawableRes; -import androidx.annotation.StringRes; - -import io.github.muntashirakon.music.R; - - -public class CategoryInfo implements Parcelable { - - public static final Creator CREATOR = new Creator() { - public CategoryInfo createFromParcel(Parcel source) { - return new CategoryInfo(source); - } - - public CategoryInfo[] newArray(int size) { - return new CategoryInfo[size]; - } - }; - public Category category; - public boolean visible; - - public CategoryInfo(Category category, boolean visible) { - this.category = category; - this.visible = visible; - } - - - private CategoryInfo(Parcel source) { - category = (Category) source.readSerializable(); - visible = source.readInt() == 1; - } - - @Override - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - dest.writeSerializable(category); - dest.writeInt(visible ? 1 : 0); - } - - public enum Category { - Home(R.id.action_home, R.string.for_you, R.drawable.ic_face), - 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), - 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; - - public final int id; - - public final int stringRes; - - Category(int id, @StringRes int stringRes, @DrawableRes int icon) { - this.stringRes = stringRes; - this.id = id; - this.icon = icon; - } - } -} diff --git a/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt b/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt new file mode 100644 index 000000000..978c2fe1b --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.kt @@ -0,0 +1,43 @@ +/* + * 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.model + +import android.os.Parcelable +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import code.name.monkey.retromusic.R +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class CategoryInfo( + val category: Category, + @get:JvmName("isVisible") + var visible: Boolean +) : Parcelable { + + enum class Category( + val id: Int, + @StringRes val stringRes: Int, + @DrawableRes val icon: Int + ) { + Home(R.id.action_home, R.string.for_you, R.drawable.ic_face), + 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_queue_music), + Genres(R.id.action_genre, R.string.genres, R.drawable.ic_guitar), + Folder(R.id.action_folder, R.string.folders, R.drawable.ic_folder); + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt b/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt index 149631a32..21adf5175 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Contributor.kt @@ -16,8 +16,10 @@ package io.github.muntashirakon.music.model import com.google.gson.annotations.SerializedName -class Contributor( +data class Contributor( val name: String, val summary: String, - val link: String, @SerializedName("profile_image") val profileImage: String + val link: String, + @SerializedName("profile_image") + val profileImage: String ) \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/deezer/DeezerResponse.kt b/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt similarity index 93% rename from app/src/main/java/io/github/muntashirakon/music/deezer/DeezerResponse.kt rename to app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt index 35ef5b8f7..9c3034061 100644 --- a/app/src/main/java/io/github/muntashirakon/music/deezer/DeezerResponse.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt @@ -1,4 +1,4 @@ -package io.github.muntashirakon.music.deezer +package io.github.muntashirakon.music.model import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Genre.kt b/app/src/main/java/io/github/muntashirakon/music/model/Genre.kt index ba33c1a0e..dc3059af3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Genre.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Genre.kt @@ -18,4 +18,8 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -data class Genre(val id: Int = -1, val name: String, val songCount: Int) : Parcelable \ No newline at end of file +data class Genre( + val id: Long, + val name: String, + val songCount: Int +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Home.kt b/app/src/main/java/io/github/muntashirakon/music/model/Home.kt index 86a9c3647..403ffbb8a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Home.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Home.kt @@ -14,10 +14,13 @@ package io.github.muntashirakon.music.model +import androidx.annotation.StringRes import io.github.muntashirakon.music.HomeSection -class Home( +data 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/io/github/muntashirakon/music/model/Playlist.kt b/app/src/main/java/io/github/muntashirakon/music/model/Playlist.kt index 26f2abb66..40ce3f48c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Playlist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Playlist.kt @@ -10,10 +10,14 @@ import org.koin.core.get @Parcelize open class Playlist( - val id: Int = -1, - val name: String = "" + val id: Long, + val name: String ) : Parcelable, KoinComponent { + companion object { + val empty = Playlist(-1, "") + } + // this default implementation covers static playlists fun getSongs(): List { return RealPlaylistRepository(get()).playlistSongs(id) @@ -27,4 +31,24 @@ open class Playlist( "" ) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Playlist + + if (id != other.id) return false + if (name != other.name) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + return result + } + + } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.java b/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.java deleted file mode 100644 index d2fc8f9c7..000000000 --- a/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.java +++ /dev/null @@ -1,58 +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 io.github.muntashirakon.music.model; - -import org.jetbrains.annotations.NotNull; - -import kotlinx.android.parcel.Parcelize; - -/** - * Created by hemanths on 3/4/19 - */ -@Parcelize -public class PlaylistSong extends Song { - - final int idInPlayList; - - final int playlistId; - - public PlaylistSong(int id, - @NotNull String title, - int trackNumber, - int year, - long duration, - @NotNull String data, - long dateModified, - int albumId, - @NotNull String albumName, - int artistId, - @NotNull String artistName, - int playlistId, - int idInPlayList, - @NotNull String composer, - String albumArtist) { - super(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName, composer, albumArtist); - this.playlistId = playlistId; - this.idInPlayList = idInPlayList; - } - - public int getIdInPlayList() { - return idInPlayList; - } - - public int getPlaylistId() { - return playlistId; - } -} diff --git a/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt b/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt new file mode 100644 index 000000000..8b7cbc00b --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.kt @@ -0,0 +1,99 @@ +/* + * 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.model + +import kotlinx.android.parcel.Parcelize + +/** + * Created by hemanths on 3/4/19 + */ +@Parcelize +class PlaylistSong( + override val id: Long, + override val title: String, + override val trackNumber: Int, + override val year: Int, + override val duration: Long, + override val data: String, + override val dateModified: Long, + override val albumId: Long, + override val albumName: String, + override val artistId: Long, + override val artistName: String, + val playlistId: Long, + val idInPlayList: Long, + override val composer: String, + override val albumArtist: String? +) : Song( + id = id, + title = title, + trackNumber = trackNumber, + year = year, + duration = duration, + data = data, + dateModified = dateModified, + albumId = albumId, + albumName = albumName, + artistId = artistId, + artistName = artistName, + composer = composer, + albumArtist = albumArtist +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as PlaylistSong + + if (id != other.id) return false + if (title != other.title) return false + if (trackNumber != other.trackNumber) return false + if (year != other.year) return false + if (duration != other.duration) return false + if (data != other.data) return false + if (dateModified != other.dateModified) return false + if (albumId != other.albumId) return false + if (albumName != other.albumName) return false + if (artistId != other.artistId) return false + if (artistName != other.artistName) return false + if (playlistId != other.playlistId) return false + if (idInPlayList != other.idInPlayList) return false + if (composer != other.composer) return false + if (albumArtist != other.albumArtist) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + trackNumber + result = 31 * result + year + result = 31 * result + duration.hashCode() + result = 31 * result + data.hashCode() + result = 31 * result + dateModified.hashCode() + result = 31 * result + albumId.hashCode() + result = 31 * result + albumName.hashCode() + result = 31 * result + artistId.hashCode() + result = 31 * result + artistName.hashCode() + result = 31 * result + playlistId.hashCode() + result = 31 * result + idInPlayList.hashCode() + result = 31 * result + composer.hashCode() + result = 31 * result + (albumArtist?.hashCode() ?: 0) + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/Song.kt b/app/src/main/java/io/github/muntashirakon/music/model/Song.kt index 85697fd4a..201f22c5c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/Song.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/Song.kt @@ -14,43 +14,88 @@ package io.github.muntashirakon.music.model import android.os.Parcelable +import code.name.monkey.retromusic.db.HistoryEntity +import code.name.monkey.retromusic.db.SongEntity import kotlinx.android.parcel.Parcelize +// update equals and hashcode if fields changes @Parcelize open class Song( - val id: Int, - val title: String, - val trackNumber: Int, - val year: Int, - val duration: Long, - val data: String, - val dateModified: Long, - val albumId: Int, - val albumName: String, - val artistId: Int, - val artistName: String, - val composer: String?, - val albumArtist: String? + open val id: Long, + open val title: String, + open val trackNumber: Int, + open val year: Int, + open val duration: Long, + open val data: String, + open val dateModified: Long, + open val albumId: Long, + open val albumName: String, + open val artistId: Long, + open val artistName: String, + open val composer: String?, + open val albumArtist: String? ) : Parcelable { + // need to override manually because is open and cannot be a data class + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Song + + if (id != other.id) return false + if (title != other.title) return false + if (trackNumber != other.trackNumber) return false + if (year != other.year) return false + if (duration != other.duration) return false + if (data != other.data) return false + if (dateModified != other.dateModified) return false + if (albumId != other.albumId) return false + if (albumName != other.albumName) return false + if (artistId != other.artistId) return false + if (artistName != other.artistName) return false + if (composer != other.composer) return false + if (albumArtist != other.albumArtist) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + trackNumber + result = 31 * result + year + result = 31 * result + duration.hashCode() + result = 31 * result + data.hashCode() + result = 31 * result + dateModified.hashCode() + result = 31 * result + albumId.hashCode() + result = 31 * result + albumName.hashCode() + result = 31 * result + artistId.hashCode() + result = 31 * result + artistName.hashCode() + result = 31 * result + (composer?.hashCode() ?: 0) + result = 31 * result + (albumArtist?.hashCode() ?: 0) + return result + } + + companion object { @JvmStatic val emptySong = Song( - -1, - "", - -1, - -1, - -1, - "", - -1, - -1, - "", - -1, - "", - "", - "" + id = -1, + title = "", + trackNumber = -1, + year = -1, + duration = -1, + data = "", + dateModified = -1, + albumId = -1, + albumName = "", + artistId = -1, + artistName = "", + composer = "", + albumArtist = "" ) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/AbsSmartPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/AbsSmartPlaylist.kt index 2202d95a1..5711b2de8 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/AbsSmartPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/AbsSmartPlaylist.kt @@ -5,6 +5,9 @@ import io.github.muntashirakon.music.R import io.github.muntashirakon.music.model.AbsCustomPlaylist abstract class AbsSmartPlaylist( - name: String = "", + name: String, @DrawableRes val iconRes: Int = R.drawable.ic_queue_music -) : AbsCustomPlaylist(-Math.abs(31 * name.hashCode() + iconRes * name.hashCode() * 31 * 31), name) \ No newline at end of file +) : AbsCustomPlaylist( + id = PlaylistIdGenerator(name, iconRes), + name = name +) \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/HistoryPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/HistoryPlaylist.kt index 56e473dac..ca2fd3bd4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/HistoryPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/HistoryPlaylist.kt @@ -7,12 +7,11 @@ import kotlinx.android.parcel.Parcelize import org.koin.core.KoinComponent @Parcelize -class HistoryPlaylist : - AbsSmartPlaylist( - App.getContext().getString(R.string.history), - R.drawable.ic_history - ), - KoinComponent { +class HistoryPlaylist : AbsSmartPlaylist( + name = App.getContext().getString(R.string.history), + iconRes = R.drawable.ic_history +), KoinComponent { + override fun songs(): List { return topPlayedRepository.recentlyPlayedTracks() } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/LastAddedPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/LastAddedPlaylist.kt index fabd66608..94b50b8f1 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/LastAddedPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/LastAddedPlaylist.kt @@ -6,8 +6,10 @@ import io.github.muntashirakon.music.model.Song import kotlinx.android.parcel.Parcelize @Parcelize -class LastAddedPlaylist : - AbsSmartPlaylist(App.getContext().getString(R.string.last_added), R.drawable.ic_library_add) { +class LastAddedPlaylist : AbsSmartPlaylist( + name = App.getContext().getString(R.string.last_added), + iconRes = R.drawable.ic_library_add +) { override fun songs(): List { return lastAddedRepository.recentSongs() } diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/NotPlayedPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/NotPlayedPlaylist.kt index 5c00a920a..6b8f28623 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/NotPlayedPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/NotPlayedPlaylist.kt @@ -7,8 +7,8 @@ import kotlinx.android.parcel.Parcelize @Parcelize class NotPlayedPlaylist : AbsSmartPlaylist( - App.getContext().getString(R.string.not_recently_played), - R.drawable.ic_watch_later + name = App.getContext().getString(R.string.not_recently_played), + iconRes = R.drawable.ic_watch_later ) { override fun songs(): List { return topPlayedRepository.notRecentlyPlayedTracks() diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt new file mode 100644 index 000000000..f81ceb6c7 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/PlaylistIdGenerator.kt @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.model.smartplaylist + +import androidx.annotation.DrawableRes +import kotlin.math.abs + +object PlaylistIdGenerator { + + operator fun invoke(name: String, @DrawableRes iconRes: Int): Long { + return -abs(31L * name.hashCode() + iconRes * name.hashCode() * 31L * 31L) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/ShuffleAllPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/ShuffleAllPlaylist.kt index a2de672fd..a66bfa818 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/ShuffleAllPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/ShuffleAllPlaylist.kt @@ -7,8 +7,8 @@ import kotlinx.android.parcel.Parcelize @Parcelize class ShuffleAllPlaylist : AbsSmartPlaylist( - App.getContext().getString(R.string.action_shuffle_all), - R.drawable.ic_shuffle + name = App.getContext().getString(R.string.action_shuffle_all), + iconRes = R.drawable.ic_shuffle ) { override fun songs(): List { return songRepository.songs() diff --git a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/TopTracksPlaylist.kt b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/TopTracksPlaylist.kt index 1f63ed53f..811210e38 100644 --- a/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/TopTracksPlaylist.kt +++ b/app/src/main/java/io/github/muntashirakon/music/model/smartplaylist/TopTracksPlaylist.kt @@ -7,8 +7,8 @@ import kotlinx.android.parcel.Parcelize @Parcelize class TopTracksPlaylist : AbsSmartPlaylist( - App.getContext().getString(R.string.my_top_tracks), - R.drawable.ic_trending_up + name = App.getContext().getString(R.string.my_top_tracks), + iconRes = R.drawable.ic_trending_up ) { override fun songs(): List { return topPlayedRepository.topTracks() diff --git a/app/src/main/java/io/github/muntashirakon/music/deezer/DeezerApiService.kt b/app/src/main/java/io/github/muntashirakon/music/network/DeezerService.kt similarity index 92% rename from app/src/main/java/io/github/muntashirakon/music/deezer/DeezerApiService.kt rename to app/src/main/java/io/github/muntashirakon/music/network/DeezerService.kt index f251ce64a..fe1b3a0cd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/deezer/DeezerApiService.kt +++ b/app/src/main/java/io/github/muntashirakon/music/network/DeezerService.kt @@ -1,6 +1,7 @@ -package io.github.muntashirakon.music.deezer +package io.github.muntashirakon.music.network import android.content.Context +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/network/LyricsService.kt b/app/src/main/java/io/github/muntashirakon/music/network/LyricsService.kt new file mode 100644 index 000000000..0a0e04904 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/Result.kt b/app/src/main/java/io/github/muntashirakon/music/network/Result.kt similarity index 87% rename from app/src/main/java/io/github/muntashirakon/music/Result.kt rename to app/src/main/java/io/github/muntashirakon/music/network/Result.kt index e99868a98..a9953c66a 100644 --- a/app/src/main/java/io/github/muntashirakon/music/Result.kt +++ b/app/src/main/java/io/github/muntashirakon/music/network/Result.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.muntashirakon.music +package io.github.muntashirakon.music.network /** * Generic class that holds the network state @@ -21,5 +21,5 @@ package io.github.muntashirakon.music 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/io/github/muntashirakon/music/network/RetrofitClient.kt b/app/src/main/java/io/github/muntashirakon/music/network/RetrofitClient.kt index ab8f425e4..a8e189a6e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/network/RetrofitClient.kt +++ b/app/src/main/java/io/github/muntashirakon/music/network/RetrofitClient.kt @@ -1,66 +1,19 @@ package io.github.muntashirakon.music.network +import android.content.Context import io.github.muntashirakon.music.App -import io.github.muntashirakon.music.Constants.BASE_URL -import com.google.gson.Gson +import io.github.muntashirakon.music.BuildConfig +import io.github.muntashirakon.music.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/io/github/muntashirakon/music/network/conversion/LyricsConverterFactory.kt b/app/src/main/java/io/github/muntashirakon/music/network/conversion/LyricsConverterFactory.kt new file mode 100644 index 000000000..474e81c99 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/preferences/AlbumCoverStylePreferenceDialog.kt b/app/src/main/java/io/github/muntashirakon/music/preferences/AlbumCoverStylePreferenceDialog.kt index c5e8ed494..fbe111aa7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/preferences/AlbumCoverStylePreferenceDialog.kt +++ b/app/src/main/java/io/github/muntashirakon/music/preferences/AlbumCoverStylePreferenceDialog.kt @@ -96,7 +96,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/io/github/muntashirakon/music/preferences/LibraryPreference.kt b/app/src/main/java/io/github/muntashirakon/music/preferences/LibraryPreference.kt index ada1bf30d..ac54a5815 100644 --- a/app/src/main/java/io/github/muntashirakon/music/preferences/LibraryPreference.kt +++ b/app/src/main/java/io/github/muntashirakon/music/preferences/LibraryPreference.kt @@ -71,9 +71,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/io/github/muntashirakon/music/providers/SongPlayCountStore.java b/app/src/main/java/io/github/muntashirakon/music/providers/SongPlayCountStore.java index 03fe7aa37..66a878390 100644 --- a/app/src/main/java/io/github/muntashirakon/music/providers/SongPlayCountStore.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/repository/AlbumRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/AlbumRepository.kt index 601930858..8adabdf31 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/AlbumRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/AlbumRepository.kt @@ -31,7 +31,7 @@ interface AlbumRepository { fun albums(query: String): List - fun album(albumId: Int): Album + fun album(albumId: Long): Album } class RealAlbumRepository(private val songRepository: RealSongRepository) : @@ -59,71 +59,42 @@ class RealAlbumRepository(private val songRepository: RealSongRepository) : return splitIntoAlbums(songs) } - override fun album(albumId: Int): Album { - val songs = songRepository.songs( - songRepository.makeSongCursor( - AudioColumns.ALBUM_ID + "=?", - arrayOf(albumId.toString()), - getSongLoaderSortOrder() - ) + override fun album(albumId: Long): Album { + val cursor = songRepository.makeSongCursor( + AudioColumns.ALBUM_ID + "=?", + arrayOf(albumId.toString()), + getSongLoaderSortOrder() ) - val album = Album(ArrayList(songs)) + val songs = songRepository.songs(cursor) + val album = Album(albumId, songs) sortAlbumSongs(album) return album } fun splitIntoAlbums( - songs: List? + songs: List ): List { - val albums = ArrayList() - if (songs != null) { - for (song in songs) { - getOrCreateAlbum(albums, song.albumId).songs?.add(song) - } - } - for (album in albums) { - sortAlbumSongs(album) - } - return albums + return songs.groupBy { it.albumId } + .map { sortAlbumSongs(Album(it.key, it.value)) } } - private fun getOrCreateAlbum( - albums: ArrayList, - albumId: Int - ): Album { - for (album in albums) { - if (album.songs!!.isNotEmpty() && album.songs[0].albumId == albumId) { - return album + private fun sortAlbumSongs(album: Album): Album { + val songs = when (PreferenceUtil.albumDetailSongSortOrder) { + SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs.sortedWith { o1, o2 -> + o1.trackNumber.compareTo(o2.trackNumber) } + SortOrder.AlbumSongSortOrder.SONG_A_Z -> album.songs.sortedWith { o1, o2 -> + o1.title.compareTo(o2.title) + } + SortOrder.AlbumSongSortOrder.SONG_Z_A -> album.songs.sortedWith { o1, o2 -> + o2.title.compareTo(o1.title) + } + SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs.sortedWith { o1, o2 -> + o1.duration.compareTo(o2.duration) + } + else -> throw IllegalArgumentException("invalid ${PreferenceUtil.albumDetailSongSortOrder}") } - val album = Album() - albums.add(album) - return album - } - - private fun sortAlbumSongs(album: Album) { - when (PreferenceUtil.albumDetailSongSortOrder) { - SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 -> - o1.trackNumber.compareTo( - o2.trackNumber - ) - }) - SortOrder.AlbumSongSortOrder.SONG_A_Z -> album.songs?.sortWith(Comparator { o1, o2 -> - o1.title.compareTo( - o2.title - ) - }) - SortOrder.AlbumSongSortOrder.SONG_Z_A -> album.songs?.sortWith(Comparator { o1, o2 -> - o2.title.compareTo( - o1.title - ) - }) - SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs?.sortWith(Comparator { o1, o2 -> - o1.duration.compareTo( - o2.duration - ) - }) - } + return album.copy(songs = songs) } private fun getSongLoaderSortOrder(): String { diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt index 49591757f..8ea9bc694 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/ArtistRepository.kt @@ -26,7 +26,7 @@ interface ArtistRepository { fun artists(query: String): List - fun artist(artistId: Int): Artist + fun artist(artistId: Long): Artist } class RealArtistRepository( @@ -39,7 +39,16 @@ class RealArtistRepository( PreferenceUtil.artistAlbumSortOrder + ", " + PreferenceUtil.artistSongSortOrder } - + override fun artist(artistId: Long): Artist { + val songs = songRepository.songs( + songRepository.makeSongCursor( + AudioColumns.ARTIST_ID + "=?", + arrayOf(artistId.toString()), + getSongLoaderSortOrder() + ) + ) + return Artist(artistId, albumRepository.splitIntoAlbums(songs)) + } override fun artists(): List { val songs = songRepository.songs( songRepository.makeSongCursor( @@ -50,17 +59,6 @@ class RealArtistRepository( return splitIntoArtists(albumRepository.splitIntoAlbums(songs)) } - override fun artists(query: String): List { - val songs = songRepository.songs( - songRepository.makeSongCursor( - AudioColumns.ARTIST + " LIKE ?", - arrayOf("%$query%"), - getSongLoaderSortOrder() - ) - ) - return splitIntoArtists(albumRepository.splitIntoAlbums(songs)) - } - override fun albumArtists(): List { val songs = songRepository.songs( songRepository.makeSongCursor( @@ -72,49 +70,34 @@ class RealArtistRepository( return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs)) } - private fun splitIntoAlbumArtists(albums: List): List { - // First group the songs in albums by filtering each artist name - val amap = hashMapOf() - albums.forEach { - val key = it.albumArtist - if (key != null) { - val artist: Artist = if (amap[key] != null) amap[key]!! else Artist() - artist.albums?.add(it) - amap[key] = artist - } - } - return ArrayList(amap.values) - } - - override fun artist(artistId: Int): Artist { + override fun artists(query: String): List { val songs = songRepository.songs( songRepository.makeSongCursor( - AudioColumns.ARTIST_ID + "=?", - arrayOf(artistId.toString()), + AudioColumns.ARTIST + " LIKE ?", + arrayOf("%$query%"), getSongLoaderSortOrder() ) ) - return Artist(ArrayList(albumRepository.splitIntoAlbums(songs))) + return splitIntoArtists(albumRepository.splitIntoAlbums(songs)) } - fun splitIntoArtists(albums: List?): List { - val artists = mutableListOf() - if (albums != null) { - for (album in albums) { - getOrCreateArtist(artists, album.artistId).albums!!.add(album) + + private fun splitIntoAlbumArtists(albums: List): List { + return albums.groupBy { it.albumArtist } + .map { + val currentAlbums = it.value + if (albums.isNotEmpty()) { + Artist(currentAlbums[0].id, currentAlbums) + } else { + Artist.empty + } } - } - return artists } - private fun getOrCreateArtist(artists: MutableList, artistId: Int): Artist { - for (artist in artists) { - if (artist.albums!!.isNotEmpty() && artist.albums[0].songs!!.isNotEmpty() && artist.albums[0].songs!![0].artistId == artistId) { - return artist - } - } - val album = Artist() - artists.add(album) - return album + + + fun splitIntoArtists(albums: List): List { + return albums.groupBy { it.artistId } + .map { Artist(it.key, it.value) } } } diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt index 7c47b22bf..7be444faa 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt @@ -22,6 +22,8 @@ import android.provider.MediaStore import android.provider.MediaStore.Audio.Genres import io.github.muntashirakon.music.Constants.IS_MUSIC import io.github.muntashirakon.music.Constants.baseProjection +import io.github.muntashirakon.music.extensions.getLong +import io.github.muntashirakon.music.extensions.getString import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.util.PreferenceUtil @@ -29,7 +31,7 @@ import io.github.muntashirakon.music.util.PreferenceUtil interface GenreRepository { fun genres(): List - fun songs(genreId: Int): List + fun songs(genreId: Long): List } class RealGenreRepository( @@ -41,25 +43,25 @@ class RealGenreRepository( return getGenresFromCursor(makeGenreCursor()) } - override fun songs(genreId: Int): List { + override fun songs(genreId: Long): List { // The genres table only stores songs that have a genre specified, // so we need to get songs without a genre a different way. - return if (genreId == -1) { + return if (genreId == -1L) { getSongsWithNoGenre() } else songRepository.songs(makeGenreSongCursor(genreId)) } private fun getGenreFromCursor(cursor: Cursor): Genre { - val id = cursor.getInt(0) - val name = cursor.getString(1) + val id = cursor.getLong(Genres._ID) + val name = cursor.getString(Genres.NAME) val songCount = songs(id).size return Genre(id, name, songCount) } private fun getGenreFromCursorWithOutSongs(cursor: Cursor): Genre { - val id = cursor.getInt(0) - val name = cursor.getString(1) + val id = cursor.getLong(Genres._ID) + val name = cursor.getString(Genres.NAME) return Genre(id, name, -1) } @@ -91,9 +93,9 @@ class RealGenreRepository( ) } - private fun makeGenreSongCursor(genreId: Int): Cursor? { + private fun makeGenreSongCursor(genreId: Long): Cursor? { return contentResolver.query( - Genres.Members.getContentUri("external", genreId.toLong()), + Genres.Members.getContentUri("external", genreId), baseProjection, IS_MUSIC, null, diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistRepository.kt index 408710585..a05d8005f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistRepository.kt @@ -18,8 +18,14 @@ import android.content.ContentResolver import android.database.Cursor import android.provider.BaseColumns import android.provider.MediaStore +import android.provider.MediaStore.Audio.AudioColumns +import android.provider.MediaStore.Audio.Playlists.* import android.provider.MediaStore.Audio.PlaylistsColumns import io.github.muntashirakon.music.Constants +import io.github.muntashirakon.music.extensions.getInt +import io.github.muntashirakon.music.extensions.getLong +import io.github.muntashirakon.music.extensions.getString +import io.github.muntashirakon.music.extensions.getStringOrNull import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.PlaylistSong import io.github.muntashirakon.music.model.Song @@ -40,11 +46,11 @@ interface PlaylistRepository { fun favoritePlaylist(playlistName: String): List - fun deletePlaylist(playlistId: Int) + fun deletePlaylist(playlistId: Long) - fun playlist(playlistId: Int): Playlist + fun playlist(playlistId: Long): Playlist - fun playlistSongs(playlistId: Int): List + fun playlistSongs(playlistId: Long): List } class RealPlaylistRepository( @@ -52,19 +58,20 @@ class RealPlaylistRepository( ) : PlaylistRepository { override fun playlist(cursor: Cursor?): Playlist { - var playlist = Playlist() - if (cursor != null && cursor.moveToFirst()) { - playlist = getPlaylistFromCursorImpl(cursor) + return cursor.use { + if (cursor?.moveToFirst() == true) { + getPlaylistFromCursorImpl(cursor) + } else { + Playlist.empty + } } - cursor?.close() - return playlist } override fun playlist(playlistName: String): Playlist { return playlist(makePlaylistCursor(PlaylistsColumns.NAME + "=?", arrayOf(playlistName))) } - override fun playlist(playlistId: Int): Playlist { + override fun playlist(playlistId: Long): Playlist { return playlist( makePlaylistCursor( BaseColumns._ID + "=?", @@ -101,8 +108,8 @@ class RealPlaylistRepository( ) } - override fun deletePlaylist(playlistId: Int) { - val localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI + override fun deletePlaylist(playlistId: Long) { + val localUri = EXTERNAL_CONTENT_URI val localStringBuilder = StringBuilder() localStringBuilder.append("_id IN (") localStringBuilder.append(playlistId) @@ -113,12 +120,12 @@ class RealPlaylistRepository( private fun getPlaylistFromCursorImpl( cursor: Cursor ): Playlist { - val id = cursor.getInt(0) - val name = cursor.getString(1) + val id = cursor.getLong(MediaStore.MediaColumns._ID) + val name = cursor.getString(NAME) return Playlist(id, name) } - override fun playlistSongs(playlistId: Int): List { + override fun playlistSongs(playlistId: Long): List { val songs = arrayListOf() val cursor = makePlaylistSongCursor(playlistId) @@ -131,21 +138,21 @@ class RealPlaylistRepository( return songs } - private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Int): PlaylistSong { - val id = cursor.getInt(0) - val title = cursor.getString(1) - val trackNumber = cursor.getInt(2) - val year = cursor.getInt(3) - val duration = cursor.getLong(4) - val data = cursor.getString(5) - val dateModified = cursor.getLong(6) - val albumId = cursor.getInt(7) - val albumName = cursor.getString(8) - val artistId = cursor.getInt(9) - val artistName = cursor.getString(10) - val idInPlaylist = cursor.getInt(11) - val composer = cursor.getString(12) - val albumArtist = cursor.getString(13) + private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Long): PlaylistSong { + val id = cursor.getLong(Members.AUDIO_ID) + val title = cursor.getString(AudioColumns.TITLE) + val trackNumber = cursor.getInt(AudioColumns.TRACK) + val year = cursor.getInt(AudioColumns.YEAR) + val duration = cursor.getLong(AudioColumns.DURATION) + val data = cursor.getString(AudioColumns.DATA) + val dateModified = cursor.getLong(AudioColumns.DATE_MODIFIED) + val albumId = cursor.getLong(AudioColumns.ALBUM_ID) + val albumName = cursor.getString(AudioColumns.ALBUM) + val artistId = cursor.getLong(AudioColumns.ARTIST_ID) + val artistName = cursor.getString(AudioColumns.ARTIST) + val idInPlaylist = cursor.getLong(Members._ID) + val composer = cursor.getStringOrNull(AudioColumns.COMPOSER) + val albumArtist = cursor.getStringOrNull("album_artist") return PlaylistSong( id, title, @@ -160,7 +167,7 @@ class RealPlaylistRepository( artistName, playlistId, idInPlaylist, - composer, + composer ?: "", albumArtist ) } @@ -170,37 +177,37 @@ class RealPlaylistRepository( values: Array? ): Cursor? { return contentResolver.query( - MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + EXTERNAL_CONTENT_URI, arrayOf( BaseColumns._ID, /* 0 */ PlaylistsColumns.NAME /* 1 */ ), selection, values, - MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER + DEFAULT_SORT_ORDER ) } - private fun makePlaylistSongCursor(playlistId: Int): Cursor? { + private fun makePlaylistSongCursor(playlistId: Long): Cursor? { return contentResolver.query( - MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()), + Members.getContentUri("external", playlistId), arrayOf( - MediaStore.Audio.Playlists.Members.AUDIO_ID, // 0 - MediaStore.Audio.AudioColumns.TITLE, // 1 - MediaStore.Audio.AudioColumns.TRACK, // 2 - MediaStore.Audio.AudioColumns.YEAR, // 3 - MediaStore.Audio.AudioColumns.DURATION, // 4 - MediaStore.Audio.AudioColumns.DATA, // 5 - MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6 - MediaStore.Audio.AudioColumns.ALBUM_ID, // 7 - MediaStore.Audio.AudioColumns.ALBUM, // 8 - MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 - MediaStore.Audio.AudioColumns.ARTIST, // 10 - MediaStore.Audio.Playlists.Members._ID,//11 - MediaStore.Audio.AudioColumns.COMPOSER,//12 + Members.AUDIO_ID, // 0 + AudioColumns.TITLE, // 1 + AudioColumns.TRACK, // 2 + AudioColumns.YEAR, // 3 + AudioColumns.DURATION, // 4 + AudioColumns.DATA, // 5 + AudioColumns.DATE_MODIFIED, // 6 + AudioColumns.ALBUM_ID, // 7 + AudioColumns.ALBUM, // 8 + AudioColumns.ARTIST_ID, // 9 + AudioColumns.ARTIST, // 10 + Members._ID,//11 + AudioColumns.COMPOSER,//12 "album_artist"//13 - ), Constants.IS_MUSIC, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER + ), Constants.IS_MUSIC, null, Members.DEFAULT_SORT_ORDER ) } } diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt index 3fe4b814c..e0354a979 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt @@ -18,12 +18,16 @@ import android.content.Context import android.database.Cursor import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns +import android.provider.MediaStore.Audio.Playlists.* import io.github.muntashirakon.music.Constants.IS_MUSIC +import io.github.muntashirakon.music.extensions.getInt +import io.github.muntashirakon.music.extensions.getLong +import io.github.muntashirakon.music.extensions.getString +import io.github.muntashirakon.music.extensions.getStringOrNull import io.github.muntashirakon.music.model.AbsCustomPlaylist import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.PlaylistSong import io.github.muntashirakon.music.model.Song -import java.util.* /** * Created by hemanths on 16/08/17. @@ -43,8 +47,8 @@ object PlaylistSongsLoader { } @JvmStatic - fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList { - val songs = arrayListOf() + fun getPlaylistSongList(context: Context, playlistId: Long): List { + val songs = mutableListOf() val cursor = makePlaylistSongCursor( context, @@ -65,21 +69,22 @@ object PlaylistSongsLoader { return songs } - private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Int): PlaylistSong { - val id = cursor.getInt(0) - val title = cursor.getString(1) - val trackNumber = cursor.getInt(2) - val year = cursor.getInt(3) - val duration = cursor.getLong(4) - val data = cursor.getString(5) - val dateModified = cursor.getLong(6) - val albumId = cursor.getInt(7) - val albumName = cursor.getString(8) - val artistId = cursor.getInt(9) - val artistName = cursor.getString(10) - val idInPlaylist = cursor.getInt(11) - val composer = cursor.getString(12) - val albumArtist = cursor.getString(13) + // TODO duplicated in [PlaylistRepository.kt] + private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Long): PlaylistSong { + val id = cursor.getLong(Members.AUDIO_ID) + val title = cursor.getString(AudioColumns.TITLE) + val trackNumber = cursor.getInt(AudioColumns.TRACK) + val year = cursor.getInt(AudioColumns.YEAR) + val duration = cursor.getLong(AudioColumns.DURATION) + val data = cursor.getString(AudioColumns.DATA) + val dateModified = cursor.getLong(AudioColumns.DATE_MODIFIED) + val albumId = cursor.getLong(AudioColumns.ALBUM_ID) + val albumName = cursor.getString(AudioColumns.ALBUM) + val artistId = cursor.getLong(AudioColumns.ARTIST_ID) + val artistName = cursor.getString(AudioColumns.ARTIST) + val idInPlaylist = cursor.getLong(Members._ID) + val composer = cursor.getString(AudioColumns.COMPOSER) + val albumArtist = cursor.getStringOrNull("album_artist") return PlaylistSong( id, title, @@ -99,12 +104,12 @@ object PlaylistSongsLoader { ) } - private fun makePlaylistSongCursor(context: Context, playlistId: Int): Cursor? { + private fun makePlaylistSongCursor(context: Context, playlistId: Long): Cursor? { try { return context.contentResolver.query( - MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()), + Members.getContentUri("external", playlistId), arrayOf( - MediaStore.Audio.Playlists.Members.AUDIO_ID, // 0 + Members.AUDIO_ID, // 0 AudioColumns.TITLE, // 1 AudioColumns.TRACK, // 2 AudioColumns.YEAR, // 3 @@ -115,10 +120,10 @@ object PlaylistSongsLoader { AudioColumns.ALBUM, // 8 AudioColumns.ARTIST_ID, // 9 AudioColumns.ARTIST, // 10 - MediaStore.Audio.Playlists.Members._ID,//11 + Members._ID,//11 AudioColumns.COMPOSER,//12 "album_artist"//13 - ), IS_MUSIC, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER + ), IS_MUSIC, null, Members.DEFAULT_SORT_ORDER ) } catch (e: SecurityException) { return null diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt index 808736416..041770e9c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt @@ -15,10 +15,15 @@ package io.github.muntashirakon.music.repository import android.content.Context +import androidx.lifecycle.LiveData import io.github.muntashirakon.music.* +import io.github.muntashirakon.music.db.* import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.smartplaylist.NotPlayedPlaylist import io.github.muntashirakon.music.network.LastFMService +import io.github.muntashirakon.music.network.LyricsRestService +import io.github.muntashirakon.music.network.Result +import io.github.muntashirakon.music.network.Result.* import io.github.muntashirakon.music.network.model.LastFmAlbum import io.github.muntashirakon.music.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>> - - suspend fun playlist(playlistId: Int): Playlist + fun historySong(): List + fun favorites(): LiveData> + fun observableHistorySongs(): LiveData> + fun albumById(albumId: Long): Album + fun playlistSongs(playlistEntity: PlaylistEntity): LiveData> + suspend fun fetchAlbums(): List + suspend fun albumByIdAsync(albumId: Long): 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: Long): List + suspend fun artistInfo(name: String, lang: String?, cache: String?): Result + suspend fun albumInfo(artist: String, album: String): Result + suspend fun artistById(artistId: Long): 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: Long): 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: Long, 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: Long): 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,64 +110,87 @@ 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: Long): Album = albumRepository.album(albumId) + + override fun albumById(albumId: Long): Album = albumRepository.album(albumId) + + override suspend fun fetchArtists(): List = artistRepository.artists() override suspend fun albumArtists(): List = artistRepository.albumArtists() - override suspend fun artistById(artistId: Int): Artist = artistRepository.artist(artistId) + override suspend fun artistById(artistId: Long): Artist = artistRepository.artist(artistId) override suspend fun recentArtists(): List = lastAddedRepository.recentArtists() 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 getGenre(genreId: Long): 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) = + override suspend fun playlist(playlistId: Long) = 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: Long, 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: Long): 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/io/github/muntashirakon/music/repository/RoomRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/RoomRepository.kt new file mode 100644 index 000000000..4a6eb42d2 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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: Long, 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: Long): 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: Long, 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(playlistName = 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: Long): 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/io/github/muntashirakon/music/repository/SongRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt index 76dc41d70..6bcac171f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt @@ -18,11 +18,15 @@ import android.content.Context import android.database.Cursor import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns +import android.provider.MediaStore.Audio.Media import io.github.muntashirakon.music.Constants.IS_MUSIC import io.github.muntashirakon.music.Constants.baseProjection +import io.github.muntashirakon.music.extensions.getInt +import io.github.muntashirakon.music.extensions.getLong +import io.github.muntashirakon.music.extensions.getString +import io.github.muntashirakon.music.extensions.getStringOrNull import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.providers.BlacklistStore - import io.github.muntashirakon.music.util.PreferenceUtil import java.util.* @@ -41,7 +45,7 @@ interface SongRepository { fun song(cursor: Cursor?): Song - fun song(songId: Int): Song + fun song(songId: Long): Song } class RealSongRepository(private val context: Context) : SongRepository { @@ -75,14 +79,14 @@ class RealSongRepository(private val context: Context) : SongRepository { return songs(makeSongCursor(AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))) } - override fun song(songId: Int): Song { + override fun song(songId: Long): Song { return song(makeSongCursor(AudioColumns._ID + "=?", arrayOf(songId.toString()))) } override fun songsByFilePath(filePath: String): List { return songs( makeSongCursor( - MediaStore.Audio.AudioColumns.DATA + "=?", + AudioColumns.DATA + "=?", arrayOf(filePath) ) ) @@ -91,19 +95,19 @@ class RealSongRepository(private val context: Context) : SongRepository { private fun getSongFromCursorImpl( cursor: Cursor ): Song { - val id = cursor.getInt(0) - val title = cursor.getString(1) - val trackNumber = cursor.getInt(2) - val year = cursor.getInt(3) - val duration = cursor.getLong(4) - val data = cursor.getString(5) - val dateModified = cursor.getLong(6) - val albumId = cursor.getInt(7) - val albumName = cursor.getString(8) - val artistId = cursor.getInt(9) - val artistName = cursor.getString(10) - val composer = cursor.getString(11) - val albumArtist = cursor.getString(12) + val id = cursor.getLong(AudioColumns._ID) + val title = cursor.getString(AudioColumns.TITLE) + val trackNumber = cursor.getInt(AudioColumns.TRACK) + val year = cursor.getInt(AudioColumns.YEAR) + val duration = cursor.getLong(AudioColumns.DURATION) + val data = cursor.getString(AudioColumns.DATA) + val dateModified = cursor.getLong(AudioColumns.DATE_MODIFIED) + val albumId = cursor.getLong(AudioColumns.ALBUM_ID) + val albumName = cursor.getStringOrNull(AudioColumns.ALBUM) + val artistId = cursor.getLong(AudioColumns.ARTIST_ID) + val artistName = cursor.getStringOrNull(AudioColumns.ARTIST) + val composer = cursor.getStringOrNull(AudioColumns.COMPOSER) + val albumArtist = cursor.getStringOrNull("album_artist") return Song( id, title, @@ -123,6 +127,7 @@ class RealSongRepository(private val context: Context) : SongRepository { @JvmOverloads fun makeSongCursor( + selection: String?, selectionValues: Array?, sortOrder: String = PreferenceUtil.songSortOrder @@ -138,30 +143,19 @@ class RealSongRepository(private val context: Context) : SongRepository { // Blacklist val paths = BlacklistStore.getInstance(context).paths if (paths.isNotEmpty()) { - selectionFinal = - generateBlacklistSelection( - selectionFinal, - paths.size - ) - selectionValuesFinal = - addBlacklistSelectionValues( - selectionValuesFinal, - paths - ) + selectionFinal = generateBlacklistSelection(selectionFinal, paths.size) + selectionValuesFinal = addBlacklistSelectionValues(selectionValuesFinal, paths) } 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 - } + selectionFinal + " AND " + Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000) + + return context.contentResolver.query( + Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), + baseProjection, + selectionFinal, + selectionValuesFinal, + sortOrder + ) } private fun generateBlacklistSelection( diff --git a/app/src/main/java/io/github/muntashirakon/music/repository/TopPlayedRepository.kt b/app/src/main/java/io/github/muntashirakon/music/repository/TopPlayedRepository.kt index effd5921c..a27348abd 100644 --- a/app/src/main/java/io/github/muntashirakon/music/repository/TopPlayedRepository.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/service/MediaSessionCallback.kt b/app/src/main/java/io/github/muntashirakon/music/service/MediaSessionCallback.kt index 12d7704d9..43e211f50 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/MediaSessionCallback.kt +++ b/app/src/main/java/io/github/muntashirakon/music/service/MediaSessionCallback.kt @@ -90,7 +90,7 @@ class MediaSessionCallback( } } - private fun checkAndStartPlaying(songs: ArrayList, itemId: Int) { + private fun checkAndStartPlaying(songs: ArrayList, itemId: Long) { var songIndex = MusicUtil.indexOfSongInList(songs, itemId) if (songIndex == -1) { songIndex = 0 diff --git a/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java b/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java index 71b1fc625..bdaa42579 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java +++ b/app/src/main/java/io/github/muntashirakon/music/service/MusicService.java @@ -84,11 +84,11 @@ import io.github.muntashirakon.music.util.MusicUtil; import io.github.muntashirakon.music.util.PreferenceUtil; import io.github.muntashirakon.music.util.RetroUtil; -import static io.github.muntashirakon.music.ConstantsKt.ALBUM_ART_ON_LOCKSCREEN; +import static io.github.muntashirakon.music.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; import static io.github.muntashirakon.music.ConstantsKt.BLURRED_ALBUM_ART; import static io.github.muntashirakon.music.ConstantsKt.CLASSIC_NOTIFICATION; import static io.github.muntashirakon.music.ConstantsKt.COLORED_NOTIFICATION; -import static io.github.muntashirakon.music.ConstantsKt.GAPLESS_PLAYBACK; +import static io.github.muntashirakon.music.ConstantsKt.GAP_LESS_PLAYBACK; import static io.github.muntashirakon.music.ConstantsKt.TOGGLE_HEADSET; /** @@ -618,7 +618,7 @@ public class MusicService extends Service implements break; case SHUFFLE_MODE_NONE: this.shuffleMode = shuffleMode; - int currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); + long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); playingQueue = new ArrayList<>(originalPlayingQueue); int newPosition = 0; if (getPlayingQueue() != null) { @@ -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; @@ -1054,15 +1054,17 @@ public class MusicService extends Service implements final Song song = getCurrentSong(); - intent.putExtra("id", song.getId()); - intent.putExtra("artist", song.getArtistName()); - intent.putExtra("album", song.getAlbumName()); - intent.putExtra("track", song.getTitle()); - intent.putExtra("duration", song.getDuration()); - intent.putExtra("position", (long) getSongProgressMillis()); - intent.putExtra("playing", isPlaying()); - intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); - sendStickyBroadcast(intent); + if (song != null) { + intent.putExtra("id", song.getId()); + intent.putExtra("artist", song.getArtistName()); + intent.putExtra("album", song.getAlbumName()); + intent.putExtra("track", song.getTitle()); + intent.putExtra("duration", song.getDuration()); + intent.putExtra("position", (long) getSongProgressMillis()); + intent.putExtra("playing", isPlaying()); + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); + sendStickyBroadcast(intent); + } } public void toggleShuffle() { @@ -1349,7 +1351,7 @@ public class MusicService extends Service implements 0); mediaSession = new MediaSessionCompat(this, - "M", + "Metro", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent); MediaSessionCallback mediasessionCallback = new MediaSessionCallback( diff --git a/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt index 2901f9921..71013ac1e 100644 --- a/app/src/main/java/io/github/muntashirakon/music/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 io.github.muntashirakon.music.R import io.github.muntashirakon.music.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/io/github/muntashirakon/music/util/AppRater.kt b/app/src/main/java/io/github/muntashirakon/music/util/AppRater.kt index 8ed214a5b..0f0092ce4 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/AppRater.kt +++ b/app/src/main/java/io/github/muntashirakon/music/util/AppRater.kt @@ -14,12 +14,14 @@ package io.github.muntashirakon.music.util +import android.app.Activity import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.net.Uri -import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.github.muntashirakon.music.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.play.core.review.ReviewManagerFactory object AppRater { private const val DO_NOT_SHOW_AGAIN = "do_not_show_again"// Package Name diff --git a/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java b/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java index 28a826062..49d9e5a4f 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/AutoGeneratedPlaylistBitmap.java @@ -34,7 +34,7 @@ public class AutoGeneratedPlaylistBitmap { if (songPlaylist == null) return null; long start = System.currentTimeMillis(); // lấy toàn bộ album id, loại bỏ trùng nhau - ArrayList albumID = new ArrayList<>(); + List albumID = new ArrayList<>(); for (Song song : songPlaylist) { if (!albumID.contains(song.getAlbumId())) albumID.add(song.getAlbumId()); } @@ -42,8 +42,8 @@ public class AutoGeneratedPlaylistBitmap { long start2 = System.currentTimeMillis() - start; // lấy toàn bộ art tồn tại - ArrayList art = new ArrayList(); - for (Integer id : albumID) { + List art = new ArrayList(); + for (Long id : albumID) { Bitmap bitmap = getBitmapWithAlbumId(context, id); if (bitmap != null) art.add(bitmap); if (art.size() == 6) break; @@ -168,7 +168,7 @@ public class AutoGeneratedPlaylistBitmap { else return bitmap; } - private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Integer id) { + private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Long id) { try { return Glide.with(context) .load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id)) diff --git a/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java b/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java index 308c13f6d..e52b70633 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/BitmapEditor.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/ColorUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/ColorUtil.java new file mode 100644 index 000000000..e4133392c --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/FilePathUtil.kt b/app/src/main/java/io/github/muntashirakon/music/util/FilePathUtil.kt new file mode 100644 index 000000000..da30e71c9 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/LyricUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/LyricUtil.java index 54b99e946..2436df0d7 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/LyricUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/MusicUtil.kt b/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt index c01e5488c..352110f4c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt +++ b/app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt @@ -4,21 +4,26 @@ 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 io.github.muntashirakon.music.R +import io.github.muntashirakon.music.db.SongEntity +import io.github.muntashirakon.music.extensions.getLong import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.lyrics.AbsSynchronizedLyrics import io.github.muntashirakon.music.repository.RealPlaylistRepository +import io.github.muntashirakon.music.repository.RealSongRepository import io.github.muntashirakon.music.repository.SongRepository import io.github.muntashirakon.music.service.MusicService import org.jaudiotagger.audio.AudioFileIO @@ -81,7 +86,7 @@ object MusicUtil : KoinComponent { return albumArtDir } - fun deleteAlbumArt(context: Context, albumId: Int) { + fun deleteAlbumArt(context: Context, albumId: Long) { val contentResolver = context.contentResolver val localUri = Uri.parse("content://media/external/audio/albumart") contentResolver.delete(ContentUris.withAppendedId(localUri, albumId.toLong()), null, null) @@ -110,7 +115,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 +155,7 @@ object MusicUtil : KoinComponent { } false } - if (files != null && files.size > 0) { + if (files != null && files.isNotEmpty()) { for (f in files) { try { val newLyrics = @@ -171,10 +176,9 @@ object MusicUtil : KoinComponent { return lyrics } - fun getMediaStoreAlbumCoverUri(albumId: Int): Uri { - val sArtworkUri = - Uri.parse("content://media/external/audio/albumart") - return ContentUris.withAppendedId(sArtworkUri, albumId.toLong()) + fun getMediaStoreAlbumCoverUri(albumId: Long): Uri { + val sArtworkUri = Uri.parse("content://media/external/audio/albumart") + return ContentUris.withAppendedId(sArtworkUri, albumId) } @@ -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 @@ -238,7 +249,7 @@ object MusicUtil : KoinComponent { return "$songCount $songString" } - fun getSongFileUri(songId: Int): Uri { + fun getSongFileUri(songId: Long): Uri { return ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId.toLong() @@ -257,18 +268,13 @@ object MusicUtil : KoinComponent { return if (year > 0) year.toString() else "-" } - fun indexOfSongInList(songs: List, songId: Int): Int { - for (i in songs.indices) { - if (songs[i].id == songId) { - return i - } - } - return -1 + fun indexOfSongInList(songs: List, songId: Long): Int { + return songs.indexOfFirst { it.id == songId } } fun insertAlbumArt( context: Context, - albumId: Int, + albumId: Long, path: String? ) { val contentResolver = context.contentResolver @@ -295,14 +301,14 @@ object MusicUtil : KoinComponent { fun isFavorite(context: Context, song: Song): Boolean { return PlaylistsUtil - .doPlaylistContains(context, getFavoritesPlaylist(context).id.toLong(), song.id) + .doPlaylistContains(context, getFavoritesPlaylist(context).id, song.id) } fun isFavoritePlaylist( 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 +347,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 @@ -376,7 +382,7 @@ object MusicUtil : KoinComponent { // as from the album art cache cursor.moveToFirst() while (!cursor.isAfterLast) { - val id = cursor.getInt(0) + val id = cursor.getLong(BaseColumns._ID) val song: Song = songRepository.song(id) removeFromQueue(song) cursor.moveToNext() @@ -413,6 +419,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 = cursor.getLong(BaseColumns._ID) + 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/io/github/muntashirakon/music/util/PlaylistsUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java index 76f14ec99..0293bb228 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import io.github.muntashirakon.music.R; +import io.github.muntashirakon.music.db.PlaylistWithSongs; import io.github.muntashirakon.music.helper.M3UWriter; import io.github.muntashirakon.music.model.Playlist; import io.github.muntashirakon.music.model.PlaylistSong; @@ -42,7 +43,7 @@ import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; public class PlaylistsUtil { - public static int createPlaylist(@NonNull final Context context, @Nullable final String name) { + public static long createPlaylist(@NonNull final Context context, @Nullable final String name) { int id = -1; if (name != null && name.length() > 0) { try { @@ -100,13 +101,13 @@ public class PlaylistsUtil { } } - public static void addToPlaylist(@NonNull final Context context, final Song song, final int playlistId, final boolean showToastOnFinish) { + public static void addToPlaylist(@NonNull final Context context, final Song song, final long playlistId, final boolean showToastOnFinish) { List helperList = new ArrayList<>(); helperList.add(song); addToPlaylist(context, helperList, playlistId, showToastOnFinish); } - public static void addToPlaylist(@NonNull final Context context, @NonNull final List songs, final int playlistId, final boolean showToastOnFinish) { + public static void addToPlaylist(@NonNull final Context context, @NonNull final List songs, final long playlistId, final boolean showToastOnFinish) { final int size = songs.size(); final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[]{ @@ -179,7 +180,7 @@ public class PlaylistsUtil { return ""; } - public static void removeFromPlaylist(@NonNull final Context context, @NonNull final Song song, int playlistId) { + public static void removeFromPlaylist(@NonNull final Context context, @NonNull final Song song, long playlistId) { Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( "external", playlistId); String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; @@ -192,7 +193,7 @@ public class PlaylistsUtil { } public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List songs) { - final int playlistId = songs.get(0).getPlaylistId(); + final long playlistId = songs.get(0).getPlaylistId(); Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( "external", playlistId); String[] selectionArgs = new String[songs.size()]; @@ -210,7 +211,7 @@ public class PlaylistsUtil { } } - public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final int songId) { + public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final long songId) { if (playlistId != -1) { try { Cursor c = context.getContentResolver().query( @@ -228,7 +229,7 @@ public class PlaylistsUtil { return false; } - public static boolean moveItem(@NonNull final Context context, int playlistId, int from, int to) { + public static boolean moveItem(@NonNull final Context context, long playlistId, int from, int to) { return MediaStore.Audio.Playlists.Members.moveItem(context.getContentResolver(), playlistId, from, to); } @@ -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/io/github/muntashirakon/music/util/PreferenceUtil.kt b/app/src/main/java/io/github/muntashirakon/music/util/PreferenceUtil.kt index 3f3e69239..ff2e4fed3 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/PreferenceUtil.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/RetroUtil.java b/app/src/main/java/io/github/muntashirakon/music/util/RetroUtil.java index 9e7ea0261..6f7317364 100755 --- a/app/src/main/java/io/github/muntashirakon/music/util/RetroUtil.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/util/RingtoneManager.kt b/app/src/main/java/io/github/muntashirakon/music/util/RingtoneManager.kt index 278f07f86..580cf7d11 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/RingtoneManager.kt +++ b/app/src/main/java/io/github/muntashirakon/music/util/RingtoneManager.kt @@ -73,7 +73,7 @@ class RingtoneManager(val context: Context) { } fun getDialog(context: Context): AlertDialog { - return MaterialAlertDialogBuilder(context) + return MaterialAlertDialogBuilder(context, R.style.MaterialAlertDialogTheme) .setTitle(R.string.dialog_title_set_ringtone) .setMessage(R.string.dialog_message_set_ringtone) .setPositiveButton(android.R.string.ok) { _, _ -> diff --git a/app/src/main/java/io/github/muntashirakon/music/util/color/ImageUtils.java b/app/src/main/java/io/github/muntashirakon/music/util/color/ImageUtils.java index 8985858cf..3820a8526 100644 --- a/app/src/main/java/io/github/muntashirakon/music/util/color/ImageUtils.java +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/AccentIcon.kt b/app/src/main/java/io/github/muntashirakon/music/views/AccentIcon.kt new file mode 100644 index 000000000..c48c3c417 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/ColorIconsImageView.kt b/app/src/main/java/io/github/muntashirakon/music/views/ColorIconsImageView.kt index 172b54151..40b9ed21c 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/ColorIconsImageView.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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/io/github/muntashirakon/music/views/PermissionItem.kt b/app/src/main/java/io/github/muntashirakon/music/views/PermissionItem.kt new file mode 100644 index 000000000..7e627f242 --- /dev/null +++ b/app/src/main/java/io/github/muntashirakon/music/views/PermissionItem.kt @@ -0,0 +1,43 @@ +package code.name.monkey.retromusic.views + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.widget.FrameLayout +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.util.ColorUtil +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.extensions.accentOutlineColor +import kotlinx.android.synthetic.main.item_permission.view.* + +class PermissionItem @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1, + defStyleRes: Int = -1 +) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { + init { + val attributes = context.obtainStyledAttributes(attrs, R.styleable.PermissionItem, 0, 0) + inflate(context, R.layout.item_permission, this) + + title.text = attributes.getText(R.styleable.PermissionItem_permissionTitle) + summary.text = attributes.getText(R.styleable.PermissionItem_permissionTitleSubTitle) + number.text = attributes.getText(R.styleable.PermissionItem_permissionTitleNumber) + button.text = attributes.getText(R.styleable.PermissionItem_permissionButtonTitle) + button.setIconResource( + attributes.getResourceId( + R.styleable.PermissionItem_permissionIcon, + R.drawable.ic_album + ) + ) + val color = ThemeStore.accentColor(context) + number.backgroundTintList = ColorStateList.valueOf(ColorUtil.withAlpha(color, 0.22f)) + + button.accentOutlineColor() + attributes.recycle() + } + + fun setButtonClick(callBack: () -> Unit) { + button.setOnClickListener { callBack.invoke() } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/muntashirakon/music/views/RetroShapeableImageView.kt b/app/src/main/java/io/github/muntashirakon/music/views/RetroShapeableImageView.kt index 7174c9b5d..35a2ddd50 100644 --- a/app/src/main/java/io/github/muntashirakon/music/views/RetroShapeableImageView.kt +++ b/app/src/main/java/io/github/muntashirakon/music/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 4414a60eb..65de32cff 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/drawable/ic_phonelink_ring.xml b/app/src/main/res/drawable/ic_phonelink_ring.xml new file mode 100644 index 000000000..b2c8313e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_phonelink_ring.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sd_storage.xml b/app/src/main/res/drawable/ic_sd_storage.xml new file mode 100644 index 000000000..839b9fb22 --- /dev/null +++ b/app/src/main/res/drawable/ic_sd_storage.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/font/pacifico.xml b/app/src/main/res/font/pacifico.xml deleted file mode 100644 index 40b5fa206..000000000 --- a/app/src/main/res/font/pacifico.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/layout-land/fragment_album_details.xml b/app/src/main/res/layout-land/fragment_album_details.xml index aa2457c36..a941c6859 100644 --- a/app/src/main/res/layout-land/fragment_album_details.xml +++ b/app/src/main/res/layout-land/fragment_album_details.xml @@ -6,6 +6,7 @@ android:layout_height="match_parent" android:background="?attr/colorSurface" android:orientation="vertical" + android:transitionName="@string/transition_album_art" tools:ignore="UnusedAttribute"> - + app:lrcTextSize="28sp" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bug_report_card_device_info.xml b/app/src/main/res/layout/bug_report_card_device_info.xml index 5ac4f7285..743e11aaa 100644 --- a/app/src/main/res/layout/bug_report_card_device_info.xml +++ b/app/src/main/res/layout/bug_report_card_device_info.xml @@ -2,6 +2,7 @@ diff --git a/app/src/main/res/layout/card_credit.xml b/app/src/main/res/layout/card_credit.xml index b80c6e8ab..f7900700b 100644 --- a/app/src/main/res/layout/card_credit.xml +++ b/app/src/main/res/layout/card_credit.xml @@ -2,9 +2,9 @@ + android:layout_height="match_parent" + android:transitionName="@string/transition_album_art"> + 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" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 7ff0a6f23..bb3a39c72 100755 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -54,7 +54,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/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 186b93eb6..59a58832b 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -31,7 +31,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:text="@string/app_name" + android:id="@+id/appNameText" android:textAppearance="@style/TextViewHeadline6" android:textStyle="bold" /> diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml deleted file mode 100644 index 9e45c2c96..000000000 --- a/app/src/main/res/layout/fragment_main.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_activity_recycler_view.xml b/app/src/main/res/layout/fragment_main_activity_recycler_view.xml index d546767b5..8356b9241 100644 --- a/app/src/main/res/layout/fragment_main_activity_recycler_view.xml +++ b/app/src/main/res/layout/fragment_main_activity_recycler_view.xml @@ -12,6 +12,7 @@ android:layout_height="wrap_content" android:clipToPadding="false" android:overScrollMode="never" + android:transitionGroup="true" android:scrollbars="none" app:layout_dodgeInsetEdges="bottom" tools:listitem="@layout/item_list" /> diff --git a/app/src/main/res/layout/fragment_main_settings.xml b/app/src/main/res/layout/fragment_main_settings.xml index 4b8ed0acc..48866b2d0 100644 --- a/app/src/main/res/layout/fragment_main_settings.xml +++ b/app/src/main/res/layout/fragment_main_settings.xml @@ -1,6 +1,7 @@ @@ -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 f97294c12..8062f058f 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 21ebef3d9..e7e0bd33d 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 717ed8bab..acfa02276 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 30a61347e..fac82e1ea 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 4e28bbf8e..bf6ffc263 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/item_permission.xml b/app/src/main/res/layout/item_permission.xml new file mode 100644 index 000000000..1901dca8c --- /dev/null +++ b/app/src/main/res/layout/item_permission.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/loading.xml b/app/src/main/res/layout/loading.xml index 54c6d3c17..6ae871577 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 000000000..47f15438a --- /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 852c6c480..a86a9cc01 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 1458c4158..83b9ea939 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 ef3cfc0aa..1986a05c5 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 000000000..94cbef3c0 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 ba6874195..64bcc529a 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 6c46ac4cb..1986a05c5 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 4e45638f7..08d7ea8a3 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 000000000..41be56878 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 5fe585e36..cb23a30b9 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 00668b42b..08d7ea8a3 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 23394228c..1a3f47f93 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 000000000..c775d1c80 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 bcaf1a42e..d719d1575 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 c998e2065..1a3f47f93 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 9142374eb..4f5dbff39 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 000000000..a42cd9210 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 451e815e3..314e49d5b 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 663d2450f..4f5dbff39 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 e78b0afd6..3ccac23ed 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 000000000..0282220d3 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 3f076e2d0..aac998482 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 8b5d5d8dd..3ccac23ed 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 13aa65539..f703e39ef 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="io.github.muntashirakon.music.fragments.playlists.PlaylistsFragment" tools:layout="@layout/fragment_main_activity_recycler_view" /> - - - - - - + app:argType="io.github.muntashirakon.music.db.PlaylistWithSongs" /> + app:argType="long" /> + app:argType="long" /> - - - + tools:layout="@layout/fragment_library" /> - + \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 988b4fe8f..0cb7cfaa4 100755 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -19,5 +19,5 @@ @string/action_shuffle_all @string/my_top_tracks - 🤔 + 😱 \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 86719ffbf..e2cad59d7 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -11,6 +11,7 @@ + diff --git a/app/src/main/res/values/permission_item.xml b/app/src/main/res/values/permission_item.xml new file mode 100644 index 000000000..8bf73d450 --- /dev/null +++ b/app/src/main/res/values/permission_item.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/preloaded_fonts.xml b/app/src/main/res/values/preloaded_fonts.xml deleted file mode 100644 index 229732150..000000000 --- a/app/src/main/res/values/preloaded_fonts.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - @font/pacifico - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f82fda094..018bdc9f8 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 2f11c1584..01529cc06 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/app/src/main/res/xml/pref_images.xml b/app/src/main/res/xml/pref_images.xml index f8a6dc3e7..b6ad8fa2e 100755 --- a/app/src/main/res/xml/pref_images.xml +++ b/app/src/main/res/xml/pref_images.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> = 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 194f9bfd5..ad903e079 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,14 @@ // 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() - maven { - url "https://plugins.gradle.org/m2/" - } } dependencies { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:bundletool:0.9.0' - classpath "gradle.plugin.ru.cleverpumpkin.proguard-dictionaries-generator:plugin:1.0.8" def nav_version = "2.3.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } diff --git a/gradle.properties b/gradle.properties index 181b62ccb..a25e163ff 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 + "