Conflicts:
	README.md
	app/build.gradle
	app/release/output-metadata.json
	app/src/main/java/code/name/monkey/retromusic/Result.kt
	app/src/main/java/code/name/monkey/retromusic/activities/PurchaseActivity.kt
	app/src/main/java/code/name/monkey/retromusic/activities/SupportDevelopmentActivity.kt
	app/src/main/java/code/name/monkey/retromusic/deezer/DeezerApiService.kt
	app/src/main/java/code/name/monkey/retromusic/deezer/DeezerResponse.kt
	app/src/main/java/io/github/muntashirakon/music/MainModule.kt
	app/src/main/java/io/github/muntashirakon/music/Result.kt
	app/src/main/java/io/github/muntashirakon/music/activities/MainActivity.kt
	app/src/main/java/io/github/muntashirakon/music/activities/base/AbsMusicServiceActivity.kt
	app/src/main/java/io/github/muntashirakon/music/activities/base/AbsSlidingMusicPanelActivity.kt
	app/src/main/java/io/github/muntashirakon/music/activities/tageditor/AlbumTagEditorActivity.kt
	app/src/main/java/io/github/muntashirakon/music/adapter/album/HorizontalAlbumAdapter.kt
	app/src/main/java/io/github/muntashirakon/music/adapter/playlist/PlaylistAdapter.kt
	app/src/main/java/io/github/muntashirakon/music/adapter/song/OrderablePlaylistSongAdapter.kt
	app/src/main/java/io/github/muntashirakon/music/adapter/song/PlayingQueueAdapter.kt
	app/src/main/java/io/github/muntashirakon/music/deezer/DeezerApiService.kt
	app/src/main/java/io/github/muntashirakon/music/deezer/DeezerResponse.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/AddToPlaylistDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/CreatePlaylistDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/DeletePlaylistDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsAsyncTask.java
	app/src/main/java/io/github/muntashirakon/music/dialogs/DeleteSongsDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/RemoveFromPlaylistDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/RenamePlaylistDialog.kt
	app/src/main/java/io/github/muntashirakon/music/dialogs/RetroSingleCheckedListAdapter.kt
	app/src/main/java/io/github/muntashirakon/music/extensions/ActivityEx.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/DetailListFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/LibraryViewModel.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/MiniPlayerFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumDetailsViewModel.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/albums/AlbumsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistDetailsViewModel.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/artists/ArtistsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/base/AbsPlayerFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/genres/GenresFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/home/HomeFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/library/LibraryFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/player/classic/ClassicPlayerFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/player/fit/FitPlaybackControlsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/player/gradient/GradientPlayerFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistDetailsViewModel.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/playlists/PlaylistsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/queue/PlayingQueueFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/settings/MainSettingsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/settings/OtherSettingsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/fragments/songs/SongsFragment.kt
	app/src/main/java/io/github/muntashirakon/music/glide/artistimage/ArtistImageLoader.kt
	app/src/main/java/io/github/muntashirakon/music/helper/M3UWriter.kt
	app/src/main/java/io/github/muntashirakon/music/helper/menu/GenreMenuHelper.kt
	app/src/main/java/io/github/muntashirakon/music/helper/menu/PlaylistMenuHelper.kt
	app/src/main/java/io/github/muntashirakon/music/helper/menu/SongMenuHelper.kt
	app/src/main/java/io/github/muntashirakon/music/helper/menu/SongsMenuHelper.kt
	app/src/main/java/io/github/muntashirakon/music/model/CategoryInfo.java
	app/src/main/java/io/github/muntashirakon/music/model/DeezerResponse.kt
	app/src/main/java/io/github/muntashirakon/music/model/Home.kt
	app/src/main/java/io/github/muntashirakon/music/model/PlaylistSong.java
	app/src/main/java/io/github/muntashirakon/music/network/DeezerService.kt
	app/src/main/java/io/github/muntashirakon/music/network/Result.kt
	app/src/main/java/io/github/muntashirakon/music/network/RetrofitClient.kt
	app/src/main/java/io/github/muntashirakon/music/repository/GenreRepository.kt
	app/src/main/java/io/github/muntashirakon/music/repository/PlaylistRepository.kt
	app/src/main/java/io/github/muntashirakon/music/repository/PlaylistSongsLoader.kt
	app/src/main/java/io/github/muntashirakon/music/repository/Repository.kt
	app/src/main/java/io/github/muntashirakon/music/repository/SongRepository.kt
	app/src/main/java/io/github/muntashirakon/music/service/MusicService.java
	app/src/main/java/io/github/muntashirakon/music/util/AppRater.kt
	app/src/main/java/io/github/muntashirakon/music/util/MusicUtil.kt
	app/src/main/java/io/github/muntashirakon/music/util/PlaylistsUtil.java
	app/src/main/res/font/pacifico.xml
	app/src/main/res/layout/fragment_gradient_player.xml
	app/src/main/res/layout/fragment_library.xml
	app/src/main/res/layout/fragment_main.xml
	app/src/main/res/layout/fragment_main_settings.xml
	app/src/main/res/navigation/library_graph.xml
	app/src/main/res/navigation/main_graph.xml
This commit is contained in:
Muntashir Al-Islam 2020-09-22 11:14:00 +06:00
commit 6df00b3e3a
244 changed files with 4912 additions and 2578 deletions

6
.gitignore vendored
View file

@ -38,8 +38,4 @@ obj/
captures captures
app/normal/release/ app/normal/release/
/models/ /models/
/app/release/
app/font/
app/src/debug/
/app/nofont/
/crowdin.properties

View file

@ -2,20 +2,11 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "ru.cleverpumpkin.proguard-dictionaries-generator"
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: "androidx.navigation.safeargs.kotlin"
proguardDictionaries {
dictionaryNames = [
"build/class-dictionary",
"build/package-dictionary",
"build/obfuscation-dictionary"
]
}
android { android {
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion = '30.0.0' buildToolsVersion = '30.0.1'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
@ -25,7 +16,7 @@ android {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId 'io.github.muntashirakon.Music' applicationId 'io.github.muntashirakon.Music'
versionCode 10443 versionCode 10444
versionName '3.5.10' versionName '3.5.10'
multiDexEnabled true multiDexEnabled true
@ -73,66 +64,51 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':appthemehelper') implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview: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.appcompat:appcompat:1.2.0'
implementation 'androidx.annotation:annotation:1.1.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.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1' def nav_version = "2.3.0"
implementation 'androidx.recyclerview:recyclerview:1.1.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' def room_version = "2.2.5"
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
def retrofit_version = '2.9.0' kapt "androidx.room:room-compiler:$room_version"
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 lifecycle_version = "2.2.0" def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'com.google.android.play:core-ktx:1.8.1'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4' implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
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" def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version" 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-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version" implementation "org.koin:koin-androidx-ext:$koin_version"
def nav_version = "2.3.0" implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
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'
} }

View file

@ -41,16 +41,10 @@
public *; public *;
} }
-keep class !android.support.v7.internal.view.menu.**,** {*;}
-dontwarn -dontwarn
-ignorewarnings -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.* #-dontwarn android.support.v8.renderscript.*
#-keepclassmembers class android.support.v8.renderscript.RenderScript { #-keepclassmembers class android.support.v8.renderscript.RenderScript {
# native *** rsn*(...); # native *** rsn*(...);
@ -58,8 +52,3 @@
#} #}
#-keep class org.jaudiotagger.** { *; } #-keep class org.jaudiotagger.** { *; }
-obfuscationdictionary build/obfuscation-dictionary.txt
-classobfuscationdictionary build/class-dictionary.txt
-packageobfuscationdictionary build/package-dictionary.txt

View file

@ -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"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:font="@font/google_sans_regular"
android:fontWeight="400" />
<font
android:font="@font/google_sans_medium"
android:fontWeight="600" />
<font
android:font="@font/google_sans_bold"
android:fontWeight="700" />
</font-family>

View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextViewNormal" parent="">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4.Compress" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item>
<item name="android:textSize">32sp</item>
</style>
<style name="TextViewHeadline5" parent="TextAppearance.MaterialComponents.Headline5">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewCaption" parent="TextAppearance.MaterialComponents.Caption">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline6" parent="TextAppearance.MaterialComponents.Headline6">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline3" parent="TextAppearance.MaterialComponents.Headline3">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline2" parent="TextAppearance.MaterialComponents.Headline2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewSubtitle1" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewSubtitle2" parent="TextAppearance.MaterialComponents.Subtitle2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewBody1" parent="TextAppearance.MaterialComponents.Body1">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewButton" parent="TextAppearance.MaterialComponents.Button">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewBody2" parent="TextAppearance.MaterialComponents.Body2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewOverline" parent="TextAppearance.MaterialComponents.Overline">
<item name="fontFamily">@font/sans</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textAppearance">@style/TextViewButton</item>
<item name="fontFamily">@font/sans</item>
<item name="android:textStyle">bold</item>
<item name="android:padding">0dp</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:textAppearance">@style/TextViewBody1</item>
<item name="fontFamily">@font/sans</item>
<item name="android:paddingTop">16dp</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
<item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="android:textStyle">bold</item>
<item name="fontFamily">@font/sans</item>
<item name="android:padding">16dp</item>
</style>
<style name="ToolbarTextAppearanceNormal">
<item name="android:textStyle">bold</item>
<item name="android:textAllCaps">false</item>
<item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="fontFamily">@font/sans</item>
<item name="android:textSize">20sp</item>
<item name="android:letterSpacing">0.0125</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
</resources>

View file

@ -115,6 +115,7 @@
<activity android:name=".activities.bugreport.BugReportActivity" /> <activity android:name=".activities.bugreport.BugReportActivity" />
<activity android:name=".activities.ShareInstagramStory" /> <activity android:name=".activities.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" /> <activity android:name=".activities.DriveModeActivity" />
<activity android:name=".activities.PermissionActivity" />
<activity <activity
android:name=".activities.LockScreenActivity" android:name=".activities.LockScreenActivity"
@ -252,11 +253,9 @@
<meta-data <meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule" android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" /> android:value="GlideModule" />
<meta-data <meta-data
android:name="com.android.vending.splits.required" android:name="com.android.vending.splits.required"
android:value="true" /> android:value="true" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>

View file

@ -13,13 +13,13 @@
}, },
{ {
"name": "Daksh P. Jain", "name": "Daksh P. Jain",
"summary": "Telegram group maintainer", "summary": "Support Representative & Moderator",
"link": "https://dakshpjain.eu.org", "link": "https://daksh.eu.org",
"profile_image": "https://i.imgur.com/fnYpg65.jpg" "profile_image": "https://i.imgur.com/fnYpg65.jpg"
}, },
{ {
"name": "Milind Goel", "name": "Milind Goel",
"summary": "Github & Telegram maintainer", "summary": "Support Representative & Moderator",
"link": "https://t.me/MilindGoel15", "link": "https://t.me/MilindGoel15",
"profile_image": "https://i.imgur.com/Bz4De21_d.jpg" "profile_image": "https://i.imgur.com/Bz4De21_d.jpg"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

@ -30,7 +30,7 @@ object Constants {
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp" const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md" const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
const val PINTEREST = "https://in.pinterest.com/retromusicapp/" 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 = const val IS_MUSIC =
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''" 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_GENRE = "extra_genre"
const val EXTRA_PLAYLIST = "extra_playlist" 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_ALBUM_ID = "extra_album_id"
const val EXTRA_ARTIST_ID = "extra_artist_id" const val EXTRA_ARTIST_ID = "extra_artist_id"
const val EXTRA_SONG = "extra_songs" const val EXTRA_SONG = "extra_songs"
const val EXTRA_PLAYLISTS = "extra_playlists"
const val LIBRARY_CATEGORIES = "library_categories" const val LIBRARY_CATEGORIES = "library_categories"
const val EXTRA_SONG_INFO = "extra_song_info" const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color" 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 CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification" const val COLORED_NOTIFICATION = "colored_notification"
const val CLASSIC_NOTIFICATION = "classic_notification" const val CLASSIC_NOTIFICATION = "classic_notification"
const val GAPLESS_PLAYBACK = "gapless_playback" const val GAP_LESS_PLAYBACK = "gap_less_playback"
const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen" const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
const val BLURRED_ALBUM_ART = "blurred_album_art" const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount" const val NEW_BLUR_AMOUNT = "new_blur_amount"
const val TOGGLE_HEADSET = "toggle_headset" 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 ALBUM_COVER_TRANSFORM = "album_cover_transform"
const val TAB_TEXT_MODE = "tab_text_mode" const val TAB_TEXT_MODE = "tab_text_mode"
const val LANGUAGE_NAME = "language_name" const val LANGUAGE_NAME = "language_name"
const val DIALOG_CORNER = "dialog_corner"
const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song" const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
const val ALBUM_GRID_STYLE = "album_grid_style_home" const val ALBUM_GRID_STYLE = "album_grid_style_home"
const val ARTIST_GRID_STYLE = "artist_grid_style_home" const val ARTIST_GRID_STYLE = "artist_grid_style_home"

View file

@ -23,3 +23,6 @@ const val SUGGESTIONS = 5
const val FAVOURITES = 4 const val FAVOURITES = 4
const val GENRES = 6 const val GENRES = 6
const val PLAYLISTS = 7 const val PLAYLISTS = 7
const val HISTORY_PLAYLIST = 8
const val LAST_ADDED_PLAYLIST = 9
const val TOP_PLAYED_PLAYLIST = 10

View file

@ -1,5 +1,12 @@
package io.github.muntashirakon.music 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.LibraryViewModel
import io.github.muntashirakon.music.fragments.albums.AlbumDetailsViewModel import io.github.muntashirakon.music.fragments.albums.AlbumDetailsViewModel
import io.github.muntashirakon.music.fragments.artists.ArtistDetailsViewModel 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.playlists.PlaylistDetailsViewModel
import io.github.muntashirakon.music.fragments.search.SearchViewModel import io.github.muntashirakon.music.fragments.search.SearchViewModel
import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.network.*
import io.github.muntashirakon.music.network.networkModule
import io.github.muntashirakon.music.repository.* 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.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module 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<BlackListStoreDao>().insertBlacklistPath(BlackListStoreEntity(it))
}
}
}
})
.fallbackToDestructiveMigration()
.build()
}
factory {
get<RetroDatabase>().lyricsDao()
}
factory {
get<RetroDatabase>().playlistDao()
}
factory {
get<RetroDatabase>().blackListStore()
}
factory {
get<RetroDatabase>().playCountDao()
}
factory {
get<RetroDatabase>().historyDao()
}
single {
RealRoomRepository(get(), get(), get(), get(), get())
} bind RoomRepository::class
}
private val mainModule = module {
single {
androidContext().contentResolver
}
}
private val dataModule = module { private val dataModule = module {
single { 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 } bind Repository::class
single { single {
@ -61,10 +154,6 @@ private val dataModule = module {
get() get()
) )
} }
single {
androidContext().contentResolver
}
} }
private val viewModules = module { private val viewModules = module {
@ -73,21 +162,21 @@ private val viewModules = module {
LibraryViewModel(get()) LibraryViewModel(get())
} }
viewModel { (albumId: Int) -> viewModel { (albumId: Long) ->
AlbumDetailsViewModel( AlbumDetailsViewModel(
get(), get(),
albumId albumId
) )
} }
viewModel { (artistId: Int) -> viewModel { (artistId: Long) ->
ArtistDetailsViewModel( ArtistDetailsViewModel(
get(), get(),
artistId artistId
) )
} }
viewModel { (playlist: Playlist) -> viewModel { (playlist: PlaylistWithSongs) ->
PlaylistDetailsViewModel( PlaylistDetailsViewModel(
get(), get(),
playlist playlist
@ -106,4 +195,4 @@ private val viewModules = module {
} }
} }
val appModules = listOf(dataModule, viewModules, networkModule) val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)

View file

@ -218,7 +218,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
.build() .build()
.transform(BlurTransformation.Builder(this).build()) .transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) { .into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
} }
}) })
} }

View file

@ -3,30 +3,23 @@ package io.github.muntashirakon.music.activities
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.* import io.github.muntashirakon.music.*
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
import io.github.muntashirakon.music.extensions.findNavController import io.github.muntashirakon.music.extensions.findNavController
import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.helper.MusicPlayerRemote
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.SearchQueryHelper.getSongs import io.github.muntashirakon.music.helper.SearchQueryHelper.getSongs
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.PlaylistSongsLoader.getPlaylistSongList import io.github.muntashirakon.music.repository.PlaylistSongsLoader
import io.github.muntashirakon.music.repository.Repository
import io.github.muntashirakon.music.service.MusicService 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 io.github.muntashirakon.music.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.util.*
class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener {
companion object { companion object {
@ -35,11 +28,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
const val APP_UPDATE_REQUEST_CODE = 9002 const val APP_UPDATE_REQUEST_CODE = 9002
} }
private val repository by inject<Repository>()
private val libraryViewModel by inject<LibraryViewModel>()
private var blockRequestPermissions = false
override fun createContentView(): View { override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_main_content) return wrapSlidingMusicPanel(R.layout.activity_main_content)
} }
@ -47,13 +35,15 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar() setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!hasPermissions()) {
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
}
setStatusbarColorAuto() setStatusbarColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
hideStatusBar() hideStatusBar()
appLaunched(this) AppRater.appLaunched(this)
addMusicServiceEventListener(libraryViewModel)
updateTabs() 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?) { 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 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() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
handlePlaybackIntent(intent)
}
private fun handlePlaybackIntent(intent: Intent?) {
if (intent == null) { if (intent == null) {
return return
} }
val uri = intent.data handlePlaybackIntent(intent)
val mimeType = intent.type }
private fun handlePlaybackIntent(intent: Intent) {
lifecycleScope.launch(IO) {
val uri: Uri? = intent.data
val mimeType: String? = intent.type
var handled = false var handled = false
if (intent.action != null && (intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) if (intent.action != null &&
intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
) { ) {
val songs: List<Song> = val songs: List<Song> = getSongs(intent.extras!!)
getSongs(this, intent.extras!!) if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
if (shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) { MusicPlayerRemote.openAndShuffleQueue(songs, true)
openAndShuffleQueue(songs, true)
} else { } else {
openQueue(songs, 0, true) MusicPlayerRemote.openQueue(songs, 0, true)
} }
handled = true handled = true
} }
if (uri != null && uri.toString().isNotEmpty()) { if (uri != null && uri.toString().isNotEmpty()) {
playFromUri(uri) MusicPlayerRemote.playFromUri(uri)
handled = true handled = true
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) { } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt() val id = parseLongFromIntent(intent, "playlistId", "playlist")
if (id >= 0) { if (id >= 0L) {
val position = intent.getIntExtra("position", 0) val position: Int = intent.getIntExtra("position", 0)
val songs: List<Song> = val songs: List<Song> =
ArrayList(getPlaylistSongList(this, id)) PlaylistSongsLoader.getPlaylistSongList(this@MainActivity, id)
openQueue(songs, position, true) MusicPlayerRemote.openQueue(songs, position, true)
handled = true handled = true
} }
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) { } else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "albumId", "album").toInt() val id = parseLongFromIntent(intent, "albumId", "album")
if (id >= 0) { if (id >= 0L) {
lifecycleScope.launch(Dispatchers.Main) { val position: Int = intent.getIntExtra("position", 0)
val position = intent.getIntExtra("position", 0) MusicPlayerRemote.openQueue(
openQueue(repository.albumById(id).songs!!, position, true) libraryViewModel.albumById(id).songs,
position,
true
)
handled = true handled = true
} }
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) { } else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "artistId", "artist").toInt() val id = parseLongFromIntent(intent, "artistId", "artist")
if (id >= 0) { if (id >= 0L) {
lifecycleScope.launch { val position: Int = intent.getIntExtra("position", 0)
val position = intent.getIntExtra("position", 0) MusicPlayerRemote.openQueue(
openQueue(repository.artistById(id).songs, position, true) libraryViewModel.artistById(id).songs,
position,
true
)
handled = true handled = true
} }
} }
}
if (handled) { if (handled) {
setIntent(Intent()) setIntent(Intent())
} }
} }
private fun parseIdFromIntent( }
private fun parseLongFromIntent(
intent: Intent, longKey: String, intent: Intent, longKey: String,
stringKey: String stringKey: String
): Long { ): Long {
@ -167,7 +146,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
try { try {
id = idString.toLong() id = idString.toLong()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, e.message) println(e.message)
} }
} }
} }

View file

@ -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! <br>Welcome to <b>Retro <span style='color:$hexColor';>Music</span></b>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
}
}

View file

@ -104,7 +104,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
} }
} }
}) })
val fastScroller = ThemedFastScroller.create(recyclerView) ThemedFastScroller.create(recyclerView)
} }
private fun checkForPadding() { private fun checkForPadding() {

View file

@ -46,7 +46,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
if (!hasPermissions()) { if (!hasPermissions()) {
requestPermissions() //requestPermissions()
} }
} }

View file

@ -4,17 +4,23 @@ import android.Manifest
import android.content.* import android.content.*
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.R 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.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.service.MusicService.* 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.lang.ref.WeakReference
import java.util.* import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener { abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>() private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>()
private val repository: RealRepository by inject()
private var serviceToken: MusicPlayerRemote.ServiceToken? = null private var serviceToken: MusicPlayerRemote.ServiceToken? = null
private var musicStateReceiver: MusicStateReceiver? = null private var musicStateReceiver: MusicStateReceiver? = null
private var receiverRegistered: Boolean = false private var receiverRegistered: Boolean = false
@ -93,6 +99,22 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
for (listener in mMusicServiceEventListeners) { for (listener in mMusicServiceEventListeners) {
listener.onPlayingMetaChanged() 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() { override fun onQueueChanged() {

View file

@ -12,11 +12,9 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil 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.R
import io.github.muntashirakon.music.RetroBottomSheetBehavior import io.github.muntashirakon.music.RetroBottomSheetBehavior
import io.github.muntashirakon.music.extensions.hide 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.extensions.whichFragment
import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.MiniPlayerFragment 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.model.CategoryInfo
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.views.BottomNavigationBarTinted import io.github.muntashirakon.music.views.BottomNavigationBarTinted
import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
@ -34,7 +33,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
} }
private val libraryViewModel by viewModel<LibraryViewModel>() protected val libraryViewModel by viewModel<LibraryViewModel>()
private lateinit var behavior: RetroBottomSheetBehavior<FrameLayout> private lateinit var behavior: RetroBottomSheetBehavior<FrameLayout>
private var miniPlayerFragment: MiniPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null
private var cps: NowPlayingScreen? = null private var cps: NowPlayingScreen? = null
@ -51,8 +50,6 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset) setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show()
dimBackground.alpha = slideOffset
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
@ -62,7 +59,6 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
} }
BottomSheetBehavior.STATE_COLLAPSED -> { BottomSheetBehavior.STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
dimBackground.hide()
} }
else -> { else -> {
@ -77,13 +73,9 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
setContentView(createContentView()) setContentView(createContentView())
chooseFragmentForTheme() chooseFragmentForTheme()
setupSlidingUpPanel() setupSlidingUpPanel()
addMusicServiceEventListener(libraryViewModel)
setupBottomSheet() setupBottomSheet()
val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY)
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
libraryViewModel.paletteColorLiveData.observe(this, Observer { libraryViewModel.paletteColorLiveData.observe(this, Observer {
this.paletteColor = it this.paletteColor = it
onPaletteColorChanged() onPaletteColorChanged()
@ -186,6 +178,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
behavior.isHideable = true behavior.isHideable = true
behavior.peekHeight = 0 behavior.peekHeight = 0
collapsePanel() collapsePanel()
ViewCompat.setElevation(slidingPanel, 0f)
ViewCompat.setElevation(bottomNavigationView, 10f) ViewCompat.setElevation(bottomNavigationView, 10f)
} else { } else {
ViewCompat.setElevation(bottomNavigationView, 10f) ViewCompat.setElevation(bottomNavigationView, 10f)

View file

@ -87,7 +87,7 @@ public class DeviceInfo {
return "Device info:\n" return "Device info:\n"
+ "---\n" + "---\n"
+ "<table>\n" + "<table>\n"
+ "<tr><td>App version</td><td>" + versionName + "</td></tr>\n" + "<tr><td><b>App version</b></td><td>" + versionName + "</td></tr>\n"
+ "<tr><td>App version code</td><td>" + versionCode + "</td></tr>\n" + "<tr><td>App version code</td><td>" + versionCode + "</td></tr>\n"
+ "<tr><td>Android build version</td><td>" + buildVersion + "</td></tr>\n" + "<tr><td>Android build version</td><td>" + buildVersion + "</td></tr>\n"
+ "<tr><td>Android release version</td><td>" + releaseVersion + "</td></tr>\n" + "<tr><td>Android release version</td><td>" + releaseVersion + "</td></tr>\n"

View file

@ -14,7 +14,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -41,7 +40,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
val repository by inject<Repository>() val repository by inject<Repository>()
lateinit var saveFab: MaterialButton lateinit var saveFab: MaterialButton
protected var id: Int = 0 protected var id: Long = 0
private set private set
private var paletteColorPrimary: Int = 0 private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false private var isInNoImageMode: Boolean = false
@ -182,12 +181,10 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
getIntentExtras() getIntentExtras()
lifecycleScope.launchWhenCreated {
songPaths = getSongPaths() songPaths = getSongPaths()
if (songPaths!!.isEmpty()) { if (songPaths!!.isEmpty()) {
finish() finish()
} }
}
setUpViews() setUpViews()
} }
@ -254,11 +251,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
private fun getIntentExtras() { private fun getIntentExtras() {
val intentExtras = intent.extras val intentExtras = intent.extras
if (intentExtras != null) { if (intentExtras != null) {
id = intentExtras.getInt(EXTRA_ID) id = intentExtras.getLong(EXTRA_ID)
} }
} }
protected abstract suspend fun getSongPaths(): List<String> protected abstract fun getSongPaths(): List<String>
protected fun searchWebFor(vararg keys: String) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() 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 { companion object {

View file

@ -14,17 +14,18 @@ import android.transition.Slide
import android.widget.Toast import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil 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.R
import io.github.muntashirakon.music.extensions.appHandleColor import io.github.muntashirakon.music.extensions.appHandleColor
import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder import io.github.muntashirakon.music.glide.palette.BitmapPaletteTranscoder
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper 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.ImageUtil
import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette import io.github.muntashirakon.music.util.RetroColorUtil.generatePalette
import io.github.muntashirakon.music.util.RetroColorUtil.getColor 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 kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import java.util.* import java.util.*
@ -44,9 +45,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
window.enterTransition = slide 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) .transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() { .into(object : SimpleTarget<BitmapPaletteWrapper>() {
@ -167,13 +168,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
) )
} }
override suspend fun getSongPaths(): List<String> { override fun getSongPaths(): List<String> {
val songs = repository.albumById(id).songs return repository.albumById(id).songs
val paths = ArrayList<String>(songs!!.size) .map(Song::data)
for (song in songs) {
paths.add(song.data)
}
return paths
} }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {

View file

@ -88,7 +88,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
writeValuesToFiles(fieldKeyValueMap, null) writeValuesToFiles(fieldKeyValueMap, null)
} }
override suspend fun getSongPaths(): List<String> { override fun getSongPaths(): List<String> {
val paths = ArrayList<String>(1) val paths = ArrayList<String>(1)
paths.add(songRepository.song(id).data) paths.add(songRepository.song(id).data)
return paths return paths

View file

@ -71,13 +71,13 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapte
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
CategoryInfo categoryInfo = categoryInfos.get(position); CategoryInfo categoryInfo = categoryInfos.get(position);
holder.checkBox.setChecked(categoryInfo.visible); holder.checkBox.setChecked(categoryInfo.isVisible());
holder.title.setText(holder.title.getResources().getString(categoryInfo.category.stringRes)); holder.title.setText(holder.title.getResources().getString(categoryInfo.getCategory().getStringRes()));
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) { if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible; categoryInfo.setVisible(!categoryInfo.isVisible());
holder.checkBox.setChecked(categoryInfo.visible); holder.checkBox.setChecked(categoryInfo.isVisible());
} else { } else {
Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category, Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
@ -110,9 +110,9 @@ public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapte
} }
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) { private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
if (categoryInfo.visible) { if (categoryInfo.isVisible()) {
for (CategoryInfo c : categoryInfos) { for (CategoryInfo c : categoryInfos) {
if (c != categoryInfo && c.visible) { if (c != categoryInfo && c.isVisible()) {
return false; return false;
} }
} }

View file

@ -1,5 +1,6 @@
package io.github.muntashirakon.music.adapter package io.github.muntashirakon.music.adapter
import android.graphics.Color
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -22,14 +23,14 @@ class GenreAdapter(
var dataSet: List<Genre>, var dataSet: List<Genre>,
private val mItemLayoutRes: Int private val mItemLayoutRes: Int
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() { ) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
val colors = listOf<Int>(Color.RED, Color.BLUE)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false)) return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position] val genre = dataSet[position]
holder.title?.text = genre.name holder.title?.text = genre.name
holder.text?.text = String.format( holder.text?.text = String.format(
Locale.getDefault(), Locale.getDefault(),

View file

@ -40,8 +40,8 @@ class HomeAdapter(
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = LayoutInflater.from(activity) val layout =
.inflate(R.layout.section_recycler_view, parent, false) LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) { return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
GENRES -> GenreViewHolder(layout) GENRES -> GenreViewHolder(layout)
@ -64,7 +64,7 @@ class HomeAdapter(
when (getItemViewType(position)) { when (getItemViewType(position)) {
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home.arrayList as List<Album>, R.string.recent_albums) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
@ -74,7 +74,7 @@ class HomeAdapter(
} }
TOP_ALBUMS -> { TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home.arrayList as List<Album>, R.string.top_albums) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
@ -84,7 +84,7 @@ class HomeAdapter(
} }
RECENT_ARTISTS -> { RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home.arrayList, R.string.recent_artists) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
@ -94,7 +94,7 @@ class HomeAdapter(
} }
TOP_ARTISTS -> { TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home.arrayList, R.string.top_artists) viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener { viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate( activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment, R.id.detailListFragment,
@ -104,15 +104,21 @@ class HomeAdapter(
} }
SUGGESTIONS -> { SUGGESTIONS -> {
val viewHolder = holder as SuggestionsViewHolder val viewHolder = holder as SuggestionsViewHolder
viewHolder.bindView(home.arrayList) viewHolder.bindView(home)
} }
FAVOURITES -> { FAVOURITES -> {
val viewHolder = holder as PlaylistViewHolder 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 -> { GENRES -> {
val viewHolder = holder as GenreViewHolder val viewHolder = holder as GenreViewHolder
viewHolder.bind(home.arrayList, R.string.genres) viewHolder.bind(home)
} }
PLAYLISTS -> { PLAYLISTS -> {
@ -130,22 +136,22 @@ class HomeAdapter(
} }
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) { private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(albums: List<Album>, titleRes: Int) { fun bindView(home: Home) {
title.text = activity.getString(titleRes) title.setText(home.titleRes)
recyclerView.apply { recyclerView.apply {
adapter = albumAdapter(albums) adapter = albumAdapter(home.arrayList as List<Album>)
layoutManager = gridLayoutManager() layoutManager = gridLayoutManager()
} }
} }
} }
private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) { private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(artists: List<Any>, titleRes: Int) { fun bindView(home: Home) {
title.setText(home.titleRes)
recyclerView.apply { recyclerView.apply {
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
adapter = artistsAdapter(artists as List<Artist>) adapter = artistsAdapter(home.arrayList as List<Artist>)
} }
title.text = activity.getString(titleRes)
} }
} }
@ -161,8 +167,7 @@ class HomeAdapter(
R.id.image8 R.id.image8
) )
fun bindView(songs: List<Any>) { fun bindView(home: Home) {
songs as List<Song>
val color = ThemeStore.accentColor(activity) val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.message).setTextColor(color) itemView.findViewById<TextView>(R.id.message).setTextColor(color)
itemView.findViewById<MaterialCardView>(R.id.card6).apply { itemView.findViewById<MaterialCardView>(R.id.card6).apply {
@ -170,9 +175,9 @@ class HomeAdapter(
} }
images.forEachIndexed { index, id -> images.forEachIndexed { index, id ->
itemView.findViewById<View>(id).setOnClickListener { itemView.findViewById<View>(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() .asBitmap()
.build() .build()
.into(itemView.findViewById(id)) .into(itemView.findViewById(id))
@ -182,35 +187,37 @@ class HomeAdapter(
} }
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) { private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(songs: List<Any>, titleRes: Int) { fun bindView(home: Home) {
arrow.hide() title.setText(home.titleRes)
recyclerView.apply { recyclerView.apply {
val songAdapter = SongAdapter( val songAdapter = SongAdapter(
activity, activity,
songs as MutableList<Song>, home.arrayList as MutableList<Song>,
R.layout.item_album_card, null R.layout.item_album_card, null
) )
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
adapter = songAdapter adapter = songAdapter
} }
title.text = activity.getString(titleRes)
} }
} }
private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) { private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) {
fun bind(genres: List<Any>, titleRes: Int) { fun bind(home: Home) {
arrow.hide() arrow.hide()
title.text = activity.getString(titleRes) title.setText(home.titleRes)
val genreAdapter = GenreAdapter(
activity,
home.arrayList as List<Genre>,
R.layout.item_grid_genre
)
recyclerView.apply { recyclerView.apply {
layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false) layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false)
val genreAdapter =
GenreAdapter(activity, genres as List<Genre>, R.layout.item_grid_genre)
adapter = genreAdapter 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 recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
val title: AppCompatTextView = itemView.findViewById(R.id.title) val title: AppCompatTextView = itemView.findViewById(R.id.title)
val arrow: ImageView = itemView.findViewById(R.id.arrow) val arrow: ImageView = itemView.findViewById(R.id.arrow)
@ -226,7 +233,7 @@ class HomeAdapter(
fun gridLayoutManager() = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false) fun gridLayoutManager() = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
fun linearLayoutManager() = LinearLayoutManager(activity, LinearLayoutManager.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( activity.findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment, R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId), 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( activity.findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),

View file

@ -60,7 +60,7 @@ class SearchAdapter(
holder.title?.text = album.title holder.title?.text = album.title
holder.text?.text = album.artistName holder.text?.text = album.artistName
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity).build().into(holder.image) .checkIgnoreMediaStore().build().into(holder.image)
} }
ARTIST -> { ARTIST -> {
val artist = dataSet.get(position) as Artist val artist = dataSet.get(position) as Artist

View file

@ -101,11 +101,10 @@ open class AlbumAdapter(
} }
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity) .checkIgnoreMediaStore()
.generatePalette(activity) .generatePalette(activity)
.build() .build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
} }
@ -137,7 +136,7 @@ open class AlbumAdapter(
private fun getSongList(albums: List<Album>): List<Song> { private fun getSongList(albums: List<Album>): List<Song> {
val songs = ArrayList<Song>() val songs = ArrayList<Song>()
for (album in albums) { for (album in albums) {
songs.addAll(album.songs!!) songs.addAll(album.songs)
} }
return songs return songs
} }
@ -171,7 +170,7 @@ open class AlbumAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
image?.let { albumClickListener?.onAlbumClick(dataSet[layoutPosition].id, it) } albumClickListener?.onAlbumClick(dataSet[layoutPosition].id, itemView)
} }
} }

View file

@ -90,6 +90,7 @@ class AlbumCoverPagerAdapter(
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
albumCover = view.findViewById(R.id.player_image) albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener { albumCover.setOnClickListener {
//LyricsDialog().show(childFragmentManager, "LyricsDialog")
showLyricsDialog() showLyricsDialog()
} }
return view return view
@ -97,7 +98,7 @@ class AlbumCoverPagerAdapter(
private fun showLyricsDialog() { private fun showLyricsDialog() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val data = MusicUtil.getLyrics(song) val data: String = MusicUtil.getLyrics(song) ?: "No lyrics found"
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder( MaterialAlertDialogBuilder(
requireContext(), requireContext(),

View file

@ -3,8 +3,6 @@ package io.github.muntashirakon.music.adapter.album
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity 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.fragments.albums.AlbumClickListener
import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.AlbumGlideRequest
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget 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.model.Album
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class HorizontalAlbumAdapter( class HorizontalAlbumAdapter(
activity: FragmentActivity, activity: FragmentActivity,
@ -30,14 +29,14 @@ class HorizontalAlbumAdapter(
} }
override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) //holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) //holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
} }
override fun loadAlbumCover(album: Album, holder: ViewHolder) { override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return if (holder.image == null) return
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity) .checkIgnoreMediaStore()
.generatePalette(activity) .generatePalette(activity)
.build() .build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {

View file

@ -113,6 +113,7 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
itemView.setOnLongClickListener(this); itemView.setOnLongClickListener(this);
} }
@Nullable
@Override @Override
public View getSwipeableContainerView() { public View getSwipeableContainerView() {
return null; return null;
@ -129,11 +130,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
} }
public void setImageTransitionName(@NonNull String transitionName) { public void setImageTransitionName(@NonNull String transitionName) {
if (imageContainerCard != null) { itemView.setTransitionName(transitionName);
/* if (imageContainerCard != null) {
imageContainerCard.setTransitionName(transitionName); imageContainerCard.setTransitionName(transitionName);
} }
if (image != null) { if (image != null) {
image.setTransitionName(transitionName); image.setTransitionName(transitionName);
} }*/
} }
} }

View file

@ -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<Playlist>,
private val layoutRes: Int,
private val playlistClickListener: PlaylistClickListener
) :
RecyclerView.Adapter<LegacyPlaylistAdapter.ViewHolder>() {
fun swapData(list: List<Playlist>) {
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)
}
}

View file

@ -13,51 +13,50 @@ import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.EXTRA_PLAYLIST
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder 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.hide
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.show
import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
import io.github.muntashirakon.music.interfaces.CabHolder 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.Playlist
import io.github.muntashirakon.music.model.Song 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.repository.PlaylistSongsLoader
import io.github.muntashirakon.music.util.AutoGeneratedPlaylistBitmap import io.github.muntashirakon.music.util.AutoGeneratedPlaylistBitmap
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroColorUtil
import java.util.*
class PlaylistAdapter( class PlaylistAdapter(
private val activity: FragmentActivity, private val activity: FragmentActivity,
var dataSet: List<Playlist>, var dataSet: List<PlaylistWithSongs>,
private var itemLayoutRes: Int, private var itemLayoutRes: Int,
cabHolder: CabHolder? cabHolder: CabHolder?
) : AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, Playlist>( ) : AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, PlaylistWithSongs>(
activity, activity,
cabHolder, cabHolder,
R.menu.menu_playlists_selection R.menu.menu_playlists_selection
) { ) {
init { init {
setHasStableIds(true) setHasStableIds(true)
} }
fun swapDataSet(dataSet: List<Playlist>) { fun swapDataSet(dataSet: List<PlaylistWithSongs>) {
this.dataSet = dataSet this.dataSet = dataSet
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun getItemId(position: Int): Long { 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -69,20 +68,20 @@ class PlaylistAdapter(
return ViewHolder(view) return ViewHolder(view)
} }
private fun getPlaylistTitle(playlist: Playlist): String { private fun getPlaylistTitle(playlist: PlaylistEntity): String {
return if (TextUtils.isEmpty(playlist.name)) "-" else playlist.name return if (TextUtils.isEmpty(playlist.playlistName)) "-" else playlist.playlistName
} }
private fun getPlaylistText(playlist: Playlist): String { private fun getPlaylistText(playlist: PlaylistWithSongs): String {
return MusicUtil.getPlaylistInfoString(activity, getSongs(playlist)) return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position] val playlist = dataSet[position]
holder.itemView.isActivated = isChecked(playlist) holder.itemView.isActivated = isChecked(playlist)
holder.title?.text = getPlaylistTitle(playlist) holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
holder.text?.text = getPlaylistText(playlist) holder.text?.text = getPlaylistText(playlist)
holder.image?.setImageDrawable(getIconRes(playlist)) holder.image?.setImageDrawable(getIconRes())
val isChecked = isChecked(playlist) val isChecked = isChecked(playlist)
if (isChecked) { if (isChecked) {
holder.menu?.hide() holder.menu?.hide()
@ -92,37 +91,25 @@ class PlaylistAdapter(
//PlaylistBitmapLoader(this, holder, playlist).execute() //PlaylistBitmapLoader(this, holder, playlist).execute()
} }
private fun getIconRes(playlist: Playlist): Drawable { private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
return if (MusicUtil.isFavoritePlaylist(activity, playlist))
TintHelper.createTintedDrawable(
activity,
R.drawable.ic_favorite,
ThemeStore.accentColor(activity)
)
else TintHelper.createTintedDrawable(
activity, activity,
R.drawable.ic_playlist_play, R.drawable.ic_playlist_play,
ATHUtil.resolveColor(activity, R.attr.colorControlNormal) ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
) )
}
override fun getItemViewType(position: Int): Int {
return if (dataSet[position] is AbsSmartPlaylist) SMART_PLAYLIST else DEFAULT_PLAYLIST
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): Playlist? { override fun getIdentifier(position: Int): PlaylistWithSongs? {
return dataSet[position] return dataSet[position]
} }
override fun getName(playlist: Playlist): String { override fun getName(playlist: PlaylistWithSongs): String {
return playlist.name return playlist.playlistEntity.playlistName
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Playlist>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<PlaylistWithSongs>) {
when (menuItem.itemId) { when (menuItem.itemId) {
else -> SongsMenuHelper.handleMenuClick( else -> SongsMenuHelper.handleMenuClick(
activity, activity,
@ -132,37 +119,26 @@ class PlaylistAdapter(
} }
} }
private fun getSongList(playlists: List<Playlist>): List<Song> { private fun getSongList(playlists: List<PlaylistWithSongs>): List<Song> {
val songs = ArrayList<Song>() val songs = mutableListOf<Song>()
for (playlist in playlists) { playlists.forEach {
if (playlist is AbsCustomPlaylist) { songs.addAll(it.songs.toSongs())
songs.addAll(playlist.songs())
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
} }
return songs return songs
} }
private fun getSongs(playlist: Playlist): List<Song> { private fun getSongs(playlist: PlaylistWithSongs): List<SongEntity> =
val songs = ArrayList<Song>() mutableListOf<SongEntity>().apply {
if (playlist is AbsSmartPlaylist) { addAll(playlist.songs)
songs.addAll(playlist.songs())
} else {
songs.addAll(playlist.getSongs())
}
return songs
} }
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
image?.apply { image?.apply {
val iconPadding = val iconPadding =
activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding) activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding)
setPadding(iconPadding, iconPadding, iconPadding, iconPadding) setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
} }
menu?.setOnClickListener { view -> menu?.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view) val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_item_playlist) popupMenu.inflate(R.menu.menu_item_playlist)
@ -221,7 +197,5 @@ class PlaylistAdapter(
companion object { companion object {
val TAG: String = PlaylistAdapter::class.java.simpleName val TAG: String = PlaylistAdapter::class.java.simpleName
private const val SMART_PLAYLIST = 0
private const val DEFAULT_PLAYLIST = 1
} }
} }

View file

@ -3,19 +3,23 @@ package io.github.muntashirakon.music.adapter.song
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.fragment.app.FragmentActivity 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.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags 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( class OrderablePlaylistSongAdapter(
private val playlist: PlaylistEntity,
activity: FragmentActivity, activity: FragmentActivity,
dataSet: ArrayList<Song>, dataSet: ArrayList<Song>,
itemLayoutRes: Int, itemLayoutRes: Int,
@ -54,8 +58,8 @@ class OrderablePlaylistSongAdapter(
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(selection as ArrayList<PlaylistSong>) RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId))
.show(activity.supportFragmentManager, "ADD_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return return
} }
} }
@ -118,7 +122,7 @@ class OrderablePlaylistSongAdapter(
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(song as PlaylistSong) RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId))
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return true return true
} }

View file

@ -4,15 +4,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity 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.R
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying 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.model.Song
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.ViewUtil 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 import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter( class PlayingQueueAdapter(
@ -153,8 +152,8 @@ class PlayingQueueAdapter(
mDragStateFlags = flags mDragStateFlags = flags
} }
override fun getSwipeableContainerView(): View? { override fun getSwipeableContainerView(): View {
return dummyContainer return dummyContainer!!
} }
} }
@ -165,18 +164,15 @@ class PlayingQueueAdapter(
private const val UP_NEXT = 2 private const val UP_NEXT = 2
} }
override fun onSwipeItem( override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? {
holder: ViewHolder?, return if (result == SwipeableItemConstants.RESULT_CANCELED) {
position: Int, @SwipeableItemResults result: Int
): SwipeResultAction {
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault() SwipeResultActionDefault()
} else { } else {
SwipedResultActionRemoveItem(this, position, activity) 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)) { return if (onCheckCanStartDrag(holder!!, position, x, y)) {
SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
} else { } 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( internal class SwipedResultActionRemoveItem(

View file

@ -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<BlackListStoreEntity>)
@Delete
suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
@Query("DELETE FROM BlackListStoreEntity")
suspend fun clearBlacklist()
@Query("SELECT * FROM BlackListStoreEntity")
fun blackListPaths(): List<BlackListStoreEntity>
}

View file

@ -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
)

View file

@ -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<HistoryEntity>
@Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
fun observableHistorySongs(): LiveData<List<HistoryEntity>>
}

View file

@ -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
)

View file

@ -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)
}

View file

@ -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
)

View file

@ -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<PlayCountEntity>
@Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC")
fun playCountSongs(): List<PlayCountEntity>
@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)
}

View file

@ -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
)

View file

@ -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<PlaylistEntity>
@Query("SELECT * FROM PlaylistEntity")
suspend fun playlists(): List<PlaylistEntity>
@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<PlaylistWithSongs>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSongsToPlaylist(songEntities: List<SongEntity>)
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun isSongExistsInPlaylist(playlistId: Long, songId: Long): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId")
fun songsFromPlaylist(playlistId: Long): LiveData<List<SongEntity>>
@Delete
suspend fun deletePlaylist(playlistEntity: PlaylistEntity)
@Delete
suspend fun deletePlaylists(playlistEntities: List<PlaylistEntity>)
@Delete
suspend fun deletePlaylistSongs(songs: List<SongEntity>)
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongsLiveData(playlistId: Long): LiveData<List<SongEntity>>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongs(playlistId: Long): List<SongEntity>
}

View file

@ -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

View file

@ -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<SongEntity>
) : Parcelable

View file

@ -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
}

View file

@ -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

View file

@ -0,0 +1,133 @@
package code.name.monkey.retromusic.db
import code.name.monkey.retromusic.model.Song
fun List<SongEntity>.toSongs(): List<Song> {
return map {
it.toSong()
}
}
fun List<Song>.toSongs(playlistId: Long): List<SongEntity> {
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<Song>.toSongsEntity(playlistEntity: PlaylistEntity): List<SongEntity> {
return map {
it.toSongEntity(playlistEntity.playListId)
}
}

View file

@ -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 package io.github.muntashirakon.music.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment 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.EXTRA_SONG
import io.github.muntashirakon.music.R 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.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog 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.model.Song
import io.github.muntashirakon.music.repository.PlaylistRepository import kotlinx.coroutines.Dispatchers
import io.github.muntashirakon.music.util.PlaylistsUtil import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AddToPlaylistDialog : DialogFragment() { class AddToPlaylistDialog : DialogFragment() {
private val playlistRepository by inject<PlaylistRepository>() private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onCreateDialog(
savedInstanceState: Bundle? companion object {
): Dialog { fun create(playlistEntities: List<PlaylistEntity>, song: Song): AddToPlaylistDialog {
val playlists = playlistRepository.playlists() val list: MutableList<Song> = mutableListOf()
val playlistNames = mutableListOf<String>() list.add(song)
return create(playlistEntities, list)
}
fun create(playlistEntities: List<PlaylistEntity>, songs: List<Song>): AddToPlaylistDialog {
return AddToPlaylistDialog().apply {
arguments = bundleOf(
EXTRA_SONG to songs,
EXTRA_PLAYLISTS to playlistEntities
)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistEntities: List<PlaylistEntity> =
extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLISTS).value
val songs: List<Song> = extraNotNull<List<Song>>(EXTRA_SONG).value
val playlistNames: MutableList<String> = mutableListOf()
playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist)) playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist))
for (p in playlists) { for (entity: PlaylistEntity in playlistEntities) {
playlistNames.add(p.name) playlistNames.add(entity.playlistName)
} }
return materialDialog(R.string.add_playlist_title) return materialDialog(R.string.add_playlist_title)
.setItems(playlistNames.toTypedArray()) { _, which -> .setItems(playlistNames.toTypedArray()) { _, which ->
val songs = extraNotNull<ArrayList<Song>>(EXTRA_SONG).value
if (which == 0) { if (which == 0) {
CreatePlaylistDialog.create(songs) CreatePlaylistDialog.create(songs)
.show(requireActivity().supportFragmentManager, "ADD_TO_PLAYLIST") .show(requireActivity().supportFragmentManager, "Dialog")
} else { } else {
PlaylistsUtil.addToPlaylist( lifecycleScope.launch(Dispatchers.IO) {
requireContext(), val songEntities: List<SongEntity> =
songs, songs.toSongsEntity(playlistEntities[which - 1])
playlists[which - 1].id, libraryViewModel.insertSongs(songEntities)
true libraryViewModel.forceReload(Playlists)
) }
} }
dismiss() dismiss()
} }
.create().colorButtons() .create().colorButtons()
} }
companion object {
fun create(song: Song): AddToPlaylistDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
fun create(songs: List<Song>): AddToPlaylistDialog {
val dialog = AddToPlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
} }

View file

@ -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 package io.github.muntashirakon.music.dialogs
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment 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.EXTRA_SONG
import io.github.muntashirakon.music.R 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.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.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.model.Song
import io.github.muntashirakon.music.util.PlaylistsUtil
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.* 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() { class CreatePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams") companion object {
override fun onCreateDialog( fun create(song: Song): CreatePlaylistDialog {
savedInstanceState: Bundle? val list = mutableListOf<Song>()
): Dialog { list.add(song)
return create(list)
}
fun create(songs: List<Song>): 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 view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null)
val songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList()
val playlistView: TextInputEditText = view.actionNewPlaylist val playlistView: TextInputEditText = view.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer
MaterialUtil.setTint(playlistContainer, false)
return materialDialog(R.string.new_playlist_title) return materialDialog(R.string.new_playlist_title)
.setView(view) .setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton( .setPositiveButton(
R.string.create_action R.string.create_action
) { _, _ -> ) { _, _ ->
val extra = extraNotNull<ArrayList<Song>>(EXTRA_SONG)
val playlistName = playlistView.text.toString() val playlistName = playlistView.text.toString()
if (!TextUtils.isEmpty(playlistName)) { if (!TextUtils.isEmpty(playlistName)) {
val playlistId = PlaylistsUtil.createPlaylist( lifecycleScope.launch(Dispatchers.IO) {
requireContext(), if (libraryViewModel.checkPlaylistExists(playlistName).isEmpty()) {
playlistView.text.toString() val playlistId: Long =
) libraryViewModel.createPlaylist(PlaylistEntity(playlistName = playlistName))
if (playlistId != -1) { libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId) })
PlaylistsUtil.addToPlaylist(requireContext(), extra.value, playlistId, true) libraryViewModel.forceReload(Playlists)
} else {
Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT)
.show()
} }
} }
} else {
playlistContainer.error = "Playlist is can't be empty"
}
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
@JvmOverloads
@JvmStatic
fun create(song: Song? = null): CreatePlaylistDialog {
val list = ArrayList<Song>()
if (song != null) {
list.add(song)
}
return create(list)
}
@JvmStatic
fun create(songs: ArrayList<Song>): CreatePlaylistDialog {
val dialog = CreatePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, songs)
dialog.arguments = args
return dialog
}
}
} }

View file

@ -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 package io.github.muntashirakon.music.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.EXTRA_PLAYLIST
import io.github.muntashirakon.music.R 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.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.util.PlaylistsUtil import io.github.muntashirakon.music.fragments.ReloadType
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeletePlaylistDialog : DialogFragment() { class DeletePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object {
fun create(playlist: PlaylistEntity): DeletePlaylistDialog {
val list = mutableListOf<PlaylistEntity>()
list.add(playlist)
return create(list)
}
fun create(playlists: List<PlaylistEntity>): DeletePlaylistDialog {
return DeletePlaylistDialog().apply {
arguments = bundleOf(EXTRA_PLAYLIST to playlists)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlists = extraNotNull<List<Playlist>>(EXTRA_PLAYLIST).value val playlists = extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLIST).value
val title: Int val title: Int
val message: CharSequence val message: CharSequence
//noinspection ConstantConditions //noinspection ConstantConditions
@ -42,7 +48,7 @@ class DeletePlaylistDialog : DialogFragment() {
} else { } else {
title = R.string.delete_playlist_title title = R.string.delete_playlist_title
message = HtmlCompat.fromHtml( 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 HtmlCompat.FROM_HTML_MODE_LEGACY
) )
} }
@ -52,26 +58,12 @@ class DeletePlaylistDialog : DialogFragment() {
.setMessage(message) .setMessage(message)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ -> .setPositiveButton(R.string.action_delete) { _, _ ->
PlaylistsUtil.deletePlaylists(requireContext(), playlists) libraryViewModel.deleteSongsFromPlaylist(playlists)
libraryViewModel.deleteRoomPlaylist(playlists)
libraryViewModel.forceReload(ReloadType.Playlists)
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
fun create(playlist: Playlist): DeletePlaylistDialog {
val list = ArrayList<Playlist>()
list.add(playlist)
return create(list)
}
fun create(playlist: ArrayList<Playlist>): DeletePlaylistDialog {
val dialog = DeletePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_PLAYLIST, playlist)
dialog.arguments = args
return dialog
}
}
} }

View file

@ -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<DeleteSongsAsyncTask.LoadingInfo, Integer, Void> {
private WeakReference<FragmentActivity> activityWeakReference;
private WeakReference<DeleteSongsDialog> 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<Uri> safUris;
public List<Song> songs;
public LoadingInfo(List<Song> songs, List<Uri> 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;
}
}
}

View file

@ -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 package io.github.muntashirakon.music.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.muntashirakon.music.EXTRA_SONG import io.github.muntashirakon.music.EXTRA_SONG
import io.github.muntashirakon.music.R 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.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog 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.helper.MusicPlayerRemote
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil 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() { class DeleteSongsDialog : DialogFragment() {
@JvmField private val libraryViewModel by sharedViewModel<LibraryViewModel>()
var currentSong: Song? = null
@JvmField
var songsToRemove: List<Song>? = null
private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(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<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable {
dismiss()
})
}
companion object { companion object {
fun create(song: Song): DeleteSongsDialog { fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>() val list = ArrayList<Song>()
list.add(song) list.add(song)
@ -119,5 +33,38 @@ class DeleteSongsDialog : DialogFragment() {
return dialog return dialog
} }
} }
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(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()
}
}

View file

@ -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<LibraryViewModel>()
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()
}
}

View file

@ -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<Repository>()
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<String> = 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
}
}
}
}
}
}

View file

@ -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<PlaylistSong>(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<PlaylistSong>
)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
.colorButtons()
}
companion object {
fun create(song: PlaylistSong): RemoveFromPlaylistDialog {
val list = ArrayList<PlaylistSong>()
list.add(song)
return create(list)
}
fun create(songs: ArrayList<PlaylistSong>): RemoveFromPlaylistDialog {
val dialog = RemoveFromPlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, songs)
dialog.arguments = args
return dialog
}
}
}

View file

@ -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<LibraryViewModel>()
companion object {
fun create(song: SongEntity): RemoveSongFromPlaylistDialog {
val list = mutableListOf<SongEntity>()
list.add(song)
return create(list)
}
fun create(songs: List<SongEntity>): RemoveSongFromPlaylistDialog {
return RemoveSongFromPlaylistDialog().apply {
arguments = bundleOf(
EXTRA_SONG to songs
)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<SongEntity>>(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()
}
}

View file

@ -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 package io.github.muntashirakon.music.dialogs
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore.Audio.Playlists.Members.PLAYLIST_ID
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment 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.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.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog 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.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RenamePlaylistDialog : DialogFragment() { class RenamePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams") companion object {
override fun onCreateDialog( fun create(playlistEntity: PlaylistEntity): RenamePlaylistDialog {
savedInstanceState: Bundle? return RenamePlaylistDialog().apply {
): Dialog { arguments = bundleOf(
val layout = LayoutInflater.from(requireContext()) EXTRA_PLAYLIST_ID to playlistEntity
.inflate(R.layout.dialog_playlist, null) )
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistEntity = extraNotNull<PlaylistEntity>(EXTRA_PLAYLIST_ID).value
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_playlist, null)
val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist) val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist)
val nameContainer: TextInputLayout = val nameContainer: TextInputLayout = layout.findViewById(R.id.actionNewPlaylistContainer)
layout.findViewById(R.id.actionNewPlaylistContainer) nameContainer.accentColor()
MaterialUtil.setTint(nameContainer, false) inputEditText.setText(playlistEntity.playlistName)
return materialDialog(R.string.rename_playlist_title) return materialDialog(R.string.rename_playlist_title)
.setView(layout) .setView(layout)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_rename) { _, _ -> .setPositiveButton(R.string.action_rename) { _, _ ->
val name = inputEditText.text.toString() val name = inputEditText.text.toString()
if (name.isNotEmpty()) { if (name.isNotEmpty()) {
PlaylistsUtil.renamePlaylist( libraryViewModel.renameRoomPlaylist(playlistEntity.playListId, name)
requireContext(), libraryViewModel.forceReload(ReloadType.Playlists)
extraNotNull<Long>(PLAYLIST_ID).value, } else {
name nameContainer.error = "Playlist name should'nt be empty"
)
} }
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
fun create(playlistId: Long): RenamePlaylistDialog {
val dialog = RenamePlaylistDialog()
val args = Bundle()
args.putLong(PLAYLIST_ID, playlistId)
dialog.arguments = args
return dialog
}
}
} }

View file

@ -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<String>
) : ArrayAdapter<String>(context, resource, objects) {
}

View file

@ -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<PlaylistWithSongs>(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()
}
}

View file

@ -53,8 +53,8 @@ class SleepTimerDialog : DialogFragment() {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
timerUpdater = TimerUpdater() timerUpdater = TimerUpdater()
val layout = LayoutInflater.from(requireContext()) val layout =
.inflate(R.layout.dialog_sleep_timer, null) LayoutInflater.from(requireContext()).inflate(R.layout.dialog_sleep_timer, null)
shouldFinishLastSong = layout.findViewById(R.id.shouldFinishLastSong) shouldFinishLastSong = layout.findViewById(R.id.shouldFinishLastSong)
seekBar = layout.findViewById(R.id.seekBar) seekBar = layout.findViewById(R.id.seekBar)
timerDisplay = layout.findViewById(R.id.timerDisplay) timerDisplay = layout.findViewById(R.id.timerDisplay)
@ -158,7 +158,7 @@ class SleepTimerDialog : DialogFragment() {
} }
} }
private inner class TimerUpdater internal constructor() : private inner class TimerUpdater() :
CountDownTimer( CountDownTimer(
PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(), PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
1000 1000

View file

@ -21,8 +21,6 @@ import android.os.Bundle
import android.text.Spanned import android.text.Spanned
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
@ -41,12 +39,6 @@ import org.jaudiotagger.tag.TagException
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
inline fun ViewGroup.forEach(action: (View) -> Unit) {
for (i in 0 until childCount) {
action(getChildAt(i))
}
}
class SongDetailDialog : DialogFragment() { class SongDetailDialog : DialogFragment() {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")

View file

@ -15,13 +15,8 @@
package io.github.muntashirakon.music.extensions package io.github.muntashirakon.music.extensions
import android.app.Activity import android.app.Activity
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) { fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
@ -30,41 +25,6 @@ fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
setSupportActionBar(toolbar) 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 <reified T : Any> Activity.extra(key: String, default: T? = null) = lazy { inline fun <reified T : Any> Activity.extra(key: String, default: T? = null) = lazy {
val value = intent?.extras?.get(key) val value = intent?.extras?.get(key)
if (value is T) value else default if (value is T) value else default

View file

@ -18,12 +18,18 @@ import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.widget.Button import android.widget.Button
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.SeekBar import android.widget.SeekBar
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.CheckResult
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil 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 code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton 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.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import io.github.muntashirakon.music.App import io.github.muntashirakon.music.App
@ -41,6 +49,7 @@ fun Int.ripAlpha(): Int {
} }
fun Dialog.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal) fun Dialog.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal)
fun Toolbar.backgroundTintList() { fun Toolbar.backgroundTintList() {
val surfaceColor = ATHUtil.resolveColor(context, R.attr.colorSurface, Color.BLACK) val surfaceColor = ATHUtil.resolveColor(context, R.attr.colorSurface, Color.BLACK)
val colorStateList = ColorStateList.valueOf(surfaceColor) 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) = fun Dialog.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(context, attr, fallBackColor) ATHUtil.resolveColor(context, attr, fallBackColor)
fun CheckBox.addAccentColor() { fun CheckBox.addAccentColor() {
buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context)) buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context))
} }
@ -91,6 +99,23 @@ fun Button.accentTextColor() {
setTextColor(ThemeStore.accentColor(App.getContext())) 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) { fun SeekBar.applyColor(@ColorInt color: Int) {
thumbTintList = ColorStateList.valueOf(color) thumbTintList = ColorStateList.valueOf(color)
progressTintList = ColorStateList.valueOf(color) progressTintList = ColorStateList.valueOf(color)
@ -107,6 +132,15 @@ fun ExtendedFloatingActionButton.accentColor() {
iconTint = textColorStateList 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) { fun MaterialButton.applyColor(color: Int) {
val backgroundColorStateList = ColorStateList.valueOf(color) val backgroundColorStateList = ColorStateList.valueOf(color)
val textColorColorStateList = ColorStateList.valueOf( val textColorColorStateList = ColorStateList.valueOf(
@ -120,6 +154,12 @@ fun MaterialButton.applyColor(color: Int) {
iconTint = textColorColorStateList iconTint = textColorColorStateList
} }
fun MaterialButton.applyOutlineColor(color: Int) {
val textColorColorStateList = ColorStateList.valueOf(color)
setTextColor(textColorColorStateList)
iconTint = textColorColorStateList
}
fun TextInputLayout.accentColor() { fun TextInputLayout.accentColor() {
val accentColor = ThemeStore.accentColor(context) val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor) val colorState = ColorStateList.valueOf(accentColor)
@ -128,6 +168,39 @@ fun TextInputLayout.accentColor() {
isHintAnimationEnabled = true 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() { fun TextInputEditText.accentColor() {
} }
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)
}

View file

@ -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)
}
}

View file

@ -8,7 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun DialogFragment.materialDialog(title: Int): MaterialAlertDialogBuilder { fun DialogFragment.materialDialog(title: Int): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder( return MaterialAlertDialogBuilder(
requireContext(), requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert R.style.MaterialAlertDialogTheme
).setTitle(title) ).setTitle(title)
} }

View file

@ -2,12 +2,15 @@ package io.github.muntashirakon.music.extensions
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.PowerManager import android.os.PowerManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@ -74,3 +77,11 @@ fun Fragment.showToast(@StringRes stringRes: Int) {
fun Fragment.showToast(message: String) { fun Fragment.showToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() 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)!!
}

View file

@ -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()
}
}

View file

@ -3,6 +3,11 @@ package io.github.muntashirakon.music.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageView 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.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager 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.album.AlbumAdapter
import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter 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.albums.AlbumClickListener
import io.github.muntashirakon.music.fragments.artists.ArtistClickListener import io.github.muntashirakon.music.fragments.artists.ArtistClickListener
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.* import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -34,6 +38,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
when (args.type) { when (args.type) {
TOP_ARTISTS -> { TOP_ARTISTS -> {
loadArtists(R.string.top_artists, TOP_ARTISTS) loadArtists(R.string.top_artists, TOP_ARTISTS)
@ -47,32 +52,88 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
loadAlbums(R.string.recent_albums, RECENT_ALBUMS) loadAlbums(R.string.recent_albums, RECENT_ALBUMS)
} }
FAVOURITES -> { FAVOURITES -> loadFavorite()
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() { private fun loadFavorite() {
toolbar.setTitle(R.string.favorites) toolbar.setTitle(R.string.favorites)
CoroutineScope(IO).launch { val songAdapter = SongAdapter(
val songs = repository.favoritePlaylistHome()
withContext(Main) {
recyclerView.apply {
adapter = SongAdapter(
requireActivity(), requireActivity(),
songs.arrayList as MutableList<Song>, mutableListOf(),
R.layout.item_list, null R.layout.item_list, null
) )
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager() 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) { private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title) toolbar.setTitle(title)
CoroutineScope(IO).launch { lifecycleScope.launch(IO) {
val artists = val artists =
if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists() if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists()
withContext(Main) { withContext(Main) {
@ -86,7 +147,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
private fun loadAlbums(title: Int, type: Int) { private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title) toolbar.setTitle(title)
CoroutineScope(IO).launch { lifecycleScope.launch(IO) {
val albums = val albums =
if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums() if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums()
withContext(Main) { withContext(Main) {
@ -120,11 +181,21 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
GridLayoutManager(requireContext(), 2, GridLayoutManager.VERTICAL, false) 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))
)
} }
} }

View file

@ -4,83 +4,130 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.fragments.ReloadType.*
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.model.* import io.github.muntashirakon.music.model.*
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LibraryViewModel( class LibraryViewModel(
private val realRepository: RealRepository private val repository: RealRepository
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>() private val paletteColor = MutableLiveData<Int>()
private val albums = MutableLiveData<List<Album>>() private val albums = MutableLiveData<List<Album>>()
private val songs = MutableLiveData<List<Song>>() private val songs = MutableLiveData<List<Song>>()
private val artists = MutableLiveData<List<Artist>>() private val artists = MutableLiveData<List<Artist>>()
private val playlists = MutableLiveData<List<Playlist>>() private val playlists = MutableLiveData<List<PlaylistWithSongs>>()
private val legacyPlaylists = MutableLiveData<List<Playlist>>()
private val genres = MutableLiveData<List<Genre>>() private val genres = MutableLiveData<List<Genre>>()
private val home = MutableLiveData<List<Home>>() private val home = MutableLiveData<List<Home>>()
val paletteColorLiveData: LiveData<Int> = paletteColor val paletteColorLiveData: LiveData<Int> = paletteColor
val homeLiveData: LiveData<List<Home>> = home
val albumsLiveData: LiveData<List<Album>> = albums
val songsLiveData: LiveData<List<Song>> = songs
val artistsLiveData: LiveData<List<Artist>> = artists
val playlisitsLiveData: LiveData<List<Playlist>> = playlists
val genresLiveData: LiveData<List<Genre>> = genres
init { init {
viewModelScope.launch { fetchHomeSections()
loadLibraryContent() }
private fun loadLibraryContent() = viewModelScope.launch(IO) {
fetchHomeSections()
fetchSongs()
fetchAlbums()
fetchArtists()
fetchGenres()
fetchPlaylists()
}
fun getSongs(): LiveData<List<Song>> {
fetchSongs()
return songs
}
fun getAlbums(): LiveData<List<Album>> {
fetchAlbums()
return albums
}
fun getArtists(): LiveData<List<Artist>> {
fetchArtists()
return artists
}
fun getPlaylists(): LiveData<List<PlaylistWithSongs>> {
fetchPlaylists()
return playlists
}
fun getLegacyPlaylist(): LiveData<List<Playlist>> {
fetchLegacyPlaylist()
return legacyPlaylists
}
fun getGenre(): LiveData<List<Genre>> {
fetchGenres()
return genres
}
fun getHome(): LiveData<List<Home>> {
return home
}
private fun fetchSongs() {
viewModelScope.launch(IO) {
songs.postValue(repository.allSongs())
} }
} }
private fun loadLibraryContent() = viewModelScope.launch { private fun fetchAlbums() {
songs.value = loadSongs.await() viewModelScope.launch(IO) {
albums.value = loadAlbums.await() albums.postValue(repository.fetchAlbums())
artists.value = loadArtists.await() }
playlists.value = loadPlaylists.await()
genres.value = loadGenres.await()
home.value = loadHome.await()
} }
private val loadHome: Deferred<List<Home>> private fun fetchArtists() {
get() = viewModelScope.async { realRepository.homeSections() } viewModelScope.launch(IO) {
artists.postValue(repository.fetchArtists())
private val loadSongs: Deferred<List<Song>> }
get() = viewModelScope.async(IO) { realRepository.allSongs() }
private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) {
realRepository.allAlbums()
} }
private val loadArtists: Deferred<List<Artist>> private fun fetchPlaylists() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.albumArtists() playlists.postValue(repository.fetchPlaylistWithSongs())
}
} }
private val loadPlaylists: Deferred<List<Playlist>> private fun fetchLegacyPlaylist() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.allPlaylists() legacyPlaylists.postValue(repository.fetchLegacyPlaylist())
}
} }
private val loadGenres: Deferred<List<Genre>> private fun fetchGenres() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.allGenres() genres.postValue(repository.fetchGenres())
}
} }
private fun fetchHomeSections() {
viewModelScope.launch(IO) {
home.postValue(repository.homeSections())
}
}
fun forceReload(reloadType: ReloadType) = viewModelScope.launch { fun forceReload(reloadType: ReloadType) = viewModelScope.launch {
when (reloadType) { when (reloadType) {
Songs -> songs.value = loadSongs.await() Songs -> fetchSongs()
Albums -> albums.value = loadAlbums.await() Albums -> fetchAlbums()
Artists -> artists.value = loadArtists.await() Artists -> fetchArtists()
HomeSections -> songs.value = loadSongs.await() HomeSections -> fetchHomeSections()
Playlists -> fetchPlaylists()
Genres -> fetchGenres()
} }
} }
@ -89,11 +136,10 @@ class LibraryViewModel(
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadLibraryContent()
println("onMediaStoreChanged") println("onMediaStoreChanged")
loadLibraryContent()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
println("onServiceConnected") println("onServiceConnected")
} }
@ -108,6 +154,7 @@ class LibraryViewModel(
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
println("onPlayingMetaChanged") println("onPlayingMetaChanged")
} }
override fun onPlayStateChanged() { override fun onPlayStateChanged() {
@ -122,11 +169,76 @@ class LibraryViewModel(
println("onShuffleModeChanged") 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<SongEntity>) = viewModelScope.launch(IO) {
repository.deleteSongsInPlaylist(songs)
}
fun deleteSongsFromPlaylist(playlists: List<PlaylistEntity>) = viewModelScope.launch(IO) {
repository.deletePlaylistSongs(playlists)
}
fun deleteRoomPlaylist(playlists: List<PlaylistEntity>) = 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<SongEntity>) = repository.insertSongs(songs)
suspend fun removeSongFromPlaylist(songEntity: SongEntity) =
repository.removeSongFromPlaylist(songEntity)
suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> =
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<Song>) = viewModelScope.launch(IO) {
repository.deleteSongs(songs)
fetchPlaylists()
loadLibraryContent()
}
} }
enum class ReloadType { enum class ReloadType {
Songs, Songs,
Albums, Albums,
Artists, Artists,
HomeSections HomeSections,
Playlists,
Genres,
} }

View file

@ -11,18 +11,14 @@ import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.*
import io.github.muntashirakon.music.extensions.textColorPrimary
import io.github.muntashirakon.music.extensions.textColorSecondary
import io.github.muntashirakon.music.fragments.base.AbsMusicServiceFragment import io.github.muntashirakon.music.fragments.base.AbsMusicServiceFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper
import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler import io.github.muntashirakon.music.helper.PlayPauseButtonOnClickHandler
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_mini_player.* import kotlinx.android.synthetic.main.fragment_mini_player.*
import kotlin.math.abs import kotlin.math.abs
@ -67,7 +63,7 @@ open class MiniPlayerFragment : AbsMusicServiceFragment(R.layout.fragment_mini_p
private fun setUpMiniPlayer() { private fun setUpMiniPlayer() {
setUpPlayPauseButton() setUpPlayPauseButton()
ViewUtil.setProgressDrawable(progressBar, ThemeStore.accentColor(requireContext())) progressBar.accentColor()
} }
private fun setUpPlayPauseButton() { 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 { class FlingPlayBackController(context: Context) : View.OnTouchListener {
private var flingPlayBackController: GestureDetector private var flingPlayBackController: GestureDetector

View file

@ -24,7 +24,6 @@ enum class NowPlayingScreen constructor(
Gradient(R.string.gradient, R.drawable.np_gradient, 17), Gradient(R.string.gradient, R.drawable.np_gradient, 17),
Material(R.string.material, R.drawable.np_material, 11), Material(R.string.material, R.drawable.np_material, 11),
Normal(R.string.normal, R.drawable.np_normal, 0), Normal(R.string.normal, R.drawable.np_normal, 0),
//Peak(R.string.peak, R.drawable.np_peak, 14), //Peak(R.string.peak, R.drawable.np_peak, 14),
Plain(R.string.plain, R.drawable.np_plain, 3), Plain(R.string.plain, R.drawable.np_plain, 3),
Simple(R.string.simple, R.drawable.np_simple, 8), Simple(R.string.simple, R.drawable.np_simple, 8),

View file

@ -6,8 +6,11 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
@ -15,7 +18,6 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper 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_ALBUM_ID
import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.EXTRA_ARTIST_ID
import io.github.muntashirakon.music.R 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.AddToPlaylistDialog
import io.github.muntashirakon.music.dialogs.DeleteSongsDialog import io.github.muntashirakon.music.dialogs.DeleteSongsDialog
import io.github.muntashirakon.music.extensions.applyColor 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.show
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.glide.AlbumGlideRequest import io.github.muntashirakon.music.glide.AlbumGlideRequest
import io.github.muntashirakon.music.glide.ArtistGlideRequest import io.github.muntashirakon.music.glide.ArtistGlideRequest
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget 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.MusicPlayerRemote
import io.github.muntashirakon.music.helper.SortOrder import io.github.muntashirakon.music.helper.SortOrder
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.Artist 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.network.model.LastFmAlbum
import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor 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_content.*
import kotlinx.android.synthetic.main.fragment_album_details.* 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.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
@ -60,29 +73,27 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private val savedSortOrder: String private val savedSortOrder: String
get() = PreferenceUtil.albumDetailSongSortOrder get() = PreferenceUtil.albumDetailSongSortOrder
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
duration = 1000L
pathMotion = MaterialArcMotion()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
toolbar.title = " "
toolbar.title = null
postponeEnterTransition() postponeEnterTransition()
detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer {
showAlbum(it)
startPostponedEnterTransition() 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() setupRecyclerView()
artistImage.setOnClickListener { artistImage.setOnClickListener {
requireActivity().findNavController(R.id.fragment_container) 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) 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 { shuffleAction.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue( MusicPlayerRemote.openAndShuffleQueue(
album.songs!!, album.songs,
true true
) )
} }
@ -134,20 +145,18 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun showAlbum(album: Album) { private fun showAlbum(album: Album) {
if (album.songs!!.isEmpty()) { if (album.songs.isEmpty()) {
return return
} }
this.album = album this.album = album
albumTitle.text = album.title albumTitle.text = album.title
val songText = val songText = resources.getQuantityString(
resources.getQuantityString(
R.plurals.albumSongs, R.plurals.albumSongs,
album.songCount, album.songCount,
album.songCount album.songCount
) )
songTitle.text = songText songTitle.text = songText
if (MusicUtil.getYearString(album.year) == "-") { if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format( albumText.text = String.format(
"%s • %s", "%s • %s",
@ -162,10 +171,25 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
) )
} }
loadAlbumCover() loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
detailsViewModel.loadArtist(album.artistId) detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer {
detailsViewModel.loadAlbumInfo(album) 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<Album>) { private fun moreAlbums(albums: List<Album>) {
@ -191,7 +215,10 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
aboutAlbumTitle.show() aboutAlbumTitle.show()
aboutAlbumTitle.text = aboutAlbumTitle.text =
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) 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()) { if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show() listeners.show()
@ -206,7 +233,11 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun loadArtistImage(artist: Artist) { private fun loadArtistImage(artist: Artist) {
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, Observer {
moreAlbums(it)
})
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.generatePalette(requireContext()) .generatePalette(requireContext())
.build() .build()
.dontAnimate() .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()) AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong())
.checkIgnoreMediaStore(requireContext()) .checkIgnoreMediaStore()
.ignoreMediaStore(PreferenceUtil.isIgnoreMediaStoreArtwork)
.generatePalette(requireContext()) .generatePalette(requireContext())
.build() .build()
.dontAnimate() .into(object : SingleColorTarget(image) {
.dontTransform() override fun onColorReady(color: Int) {
.into(object : RetroMusicColoredTarget(image) { setColors(color)
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors)
} }
}) })
} }
private fun setColors(color: MediaNotificationProcessor) { private fun setColors(color: Int) {
shuffleAction.applyColor(color.backgroundColor) shuffleAction.applyColor(color)
playAction.applyColor(color.backgroundColor) playAction.applyOutlineColor(color)
} }
override fun onAlbumClick(albumId: Int, view: View) { override fun onAlbumClick(albumId: Long, view: View) {
findNavController().navigate( findNavController().navigate(
R.id.albumDetailsFragment, 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 return true
} }
R.id.action_add_to_playlist -> { R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(childFragmentManager, "ADD_PLAYLIST") lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true return true
} }
R.id.action_delete_from_device -> { R.id.action_delete_from_device -> {
@ -326,29 +362,31 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private fun setSaveSortOrder(sortOrder: String) { private fun setSaveSortOrder(sortOrder: String) {
PreferenceUtil.albumDetailSongSortOrder = sortOrder PreferenceUtil.albumDetailSongSortOrder = sortOrder
when (sortOrder) { val songs = when (sortOrder) {
SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 -> SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs.sortedWith { o1, o2 ->
o1.trackNumber.compareTo( o1.trackNumber.compareTo(
o2.trackNumber 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( o1.title.compareTo(
o2.title 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( o2.title.compareTo(
o1.title o1.title
) )
}) }
SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs?.sortWith(Comparator { o1, o2 -> SortOrder.AlbumSongSortOrder.SONG_DURATION -> album.songs.sortedWith { o1, o2 ->
o1.duration.compareTo( o1.duration.compareTo(
o2.duration o2.duration
) )
})
} }
album.songs?.let { simpleSongAdapter.swapDataSet(it) } else -> throw IllegalArgumentException("invalid $sortOrder")
}
album = album.copy(songs = songs)
simpleSongAdapter.swapDataSet(album.songs)
} }
companion object { companion object {

View file

@ -1,68 +1,43 @@
package io.github.muntashirakon.music.fragments.albums package io.github.muntashirakon.music.fragments.albums
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.liveData
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.Artist 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.network.model.LastFmAlbum
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class AlbumDetailsViewModel( class AlbumDetailsViewModel(
private val realRepository: RealRepository, private val repository: RealRepository,
private val albumId: Int private val albumId: Long
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val _album = MutableLiveData<Album>() fun getAlbum(): LiveData<Album> = liveData(IO) {
private val _artist = MutableLiveData<Artist>() emit(repository.albumByIdAsync(albumId))
private val _lastFmAlbum = MutableLiveData<LastFmAlbum>()
private val _moreAlbums = MutableLiveData<List<Album>>()
fun getAlbum(): LiveData<Album> = _album
fun getArtist(): LiveData<Artist> = _artist
fun getAlbumInfo(): LiveData<LastFmAlbum> = _lastFmAlbum
fun getMoreAlbums(): LiveData<List<Album>> = _moreAlbums
init {
loadAlbumDetails()
} }
private fun loadAlbumDetails() = viewModelScope.launch { fun getArtist(artistId: Long): LiveData<Artist> = liveData(IO) {
val album = loadAlbumAsync.await() ?: throw NullPointerException("Album couldn't found") val artist = repository.artistById(artistId)
_album.postValue(album) emit(artist)
} }
fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) { fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData {
try { emit(Result.Loading)
val lastFmAlbum = realRepository.albumInfo( emit(repository.albumInfo(album.artistName ?: "-", album.title ?: "-"))
album.artistName ?: "-", album.title ?: "-"
)
_lastFmAlbum.postValue(lastFmAlbum)
} catch (ignored: Exception) {}
} }
fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) { fun getMoreAlbums(artist: Artist): LiveData<List<Album>> = liveData(IO) {
val artist = realRepository.artistById(artistId) artist.albums.filter { item -> item.id != albumId }.let { albums ->
_artist.postValue(artist) if (albums.isNotEmpty()) emit(albums)
artist.albums?.filter { item -> item.id != albumId }?.let { albums ->
if (albums.isNotEmpty()) _moreAlbums.postValue(albums)
} }
} }
private val loadAlbumAsync: Deferred<Album?>
get() = viewModelScope.async(Dispatchers.IO) {
realRepository.albumById(albumId)
}
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadAlbumDetails()
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View file

@ -1,26 +1,35 @@
package io.github.muntashirakon.music.fragments.albums package io.github.muntashirakon.music.fragments.albums
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.*
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.EXTRA_ALBUM_ID
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.album.AlbumAdapter 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.ReloadType
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment 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.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.transition.platform.MaterialFadeThrough
class AlbumsFragment :
AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(), class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
AlbumClickListener { AlbumClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.albumsLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getAlbums().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else
@ -40,7 +49,7 @@ class AlbumsFragment :
return AlbumAdapter( return AlbumAdapter(
requireActivity(), requireActivity(),
dataSet, dataSet,
R.layout.item_grid, itemLayoutRes(),
null, null,
this this
) )
@ -94,9 +103,8 @@ class AlbumsFragment :
} }
} }
override fun onAlbumClick(albumId: Int, view: View) { override fun onAlbumClick(albumId: Long, view: View) {
val controller = requireActivity().findNavController(R.id.fragment_container) findActivityNavController(R.id.fragment_container).navigate(
controller.navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, 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 { interface AlbumClickListener {
fun onAlbumClick(albumId: Int, view: View) fun onAlbumClick(albumId: Long, view: View)
} }

View file

@ -10,33 +10,41 @@ import android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager 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.R
import io.github.muntashirakon.music.adapter.album.HorizontalAlbumAdapter import io.github.muntashirakon.music.adapter.album.HorizontalAlbumAdapter
import io.github.muntashirakon.music.adapter.song.SimpleSongAdapter import io.github.muntashirakon.music.adapter.song.SimpleSongAdapter
import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog
import io.github.muntashirakon.music.extensions.applyColor 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.show
import io.github.muntashirakon.music.extensions.showToast import io.github.muntashirakon.music.extensions.showToast
import io.github.muntashirakon.music.fragments.albums.AlbumClickListener import io.github.muntashirakon.music.fragments.albums.AlbumClickListener
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.glide.ArtistGlideRequest 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.MusicPlayerRemote
import io.github.muntashirakon.music.model.Artist 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.network.model.LastFmArtist
import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.util.CustomArtistImageUtil import io.github.muntashirakon.music.util.CustomArtistImageUtil
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.RetroUtil 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_content.*
import kotlinx.android.synthetic.main.fragment_artist_details.* 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.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
@ -66,13 +74,10 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
setupRecyclerView() setupRecyclerView()
postponeEnterTransition() postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer { detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer {
showArtist(it) showArtist(it)
startPostponedEnterTransition() startPostponedEnterTransition()
}) })
detailsViewModel.getArtistInfo().observe(viewLifecycleOwner, Observer {
artistInfo(it)
})
playAction.apply { playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
@ -133,6 +138,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
albumTitle.text = albumText albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber }) songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
artist.albums?.let { albumAdapter.swapDataSet(it) } artist.albums?.let { albumAdapter.swapDataSet(it) }
} }
private fun loadBiography( private fun loadBiography(
@ -141,7 +147,14 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
) { ) {
biography = null biography = null
this.lang = lang 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?) { private fun artistInfo(lastFmArtist: LastFmArtist?) {
@ -175,23 +188,24 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
private fun loadArtistImage(artist: Artist) { private fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.generatePalette(requireContext()).build() .generatePalette(requireContext()).build()
.dontAnimate().into(object : RetroMusicColoredTarget(image) { .dontAnimate()
override fun onColorReady(colors: MediaNotificationProcessor) { .into(object : SingleColorTarget(image) {
startPostponedEnterTransition() override fun onColorReady(color: Int) {
setColors(colors) setColors(color)
} }
}) })
} }
private fun setColors(color: MediaNotificationProcessor) { private fun setColors(color: Int) {
shuffleAction.applyColor(color.backgroundColor) shuffleAction.applyColor(color)
playAction.applyColor(color.backgroundColor) playAction.applyOutlineColor(color)
} }
override fun onAlbumClick(albumId: Int, view: View) {
override fun onAlbumClick(albumId: Long, view: View) {
findNavController().navigate( findNavController().navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf("extra_album_id" to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to getString(R.string.transition_album_art) view to getString(R.string.transition_album_art)
@ -216,7 +230,13 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
return true return true
} }
R.id.action_add_to_playlist -> { R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(childFragmentManager, "ADD_PLAYLIST") lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true return true
} }
R.id.action_set_artist_image -> { R.id.action_set_artist_image -> {

View file

@ -1,51 +1,37 @@
package io.github.muntashirakon.music.fragments.artists package io.github.muntashirakon.music.fragments.artists
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.liveData
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.model.Artist 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.network.model.LastFmArtist
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private val artistId: Int private val artistId: Long
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?> fun getArtist(): LiveData<Artist> = liveData(IO) {
get() = viewModelScope.async(Dispatchers.IO) { val artist = realRepository.artistById(artistId)
realRepository.artistById(artistId) emit(artist)
} }
private val _artist = MutableLiveData<Artist>() fun getArtistInfo(
private val _lastFmArtist = MutableLiveData<LastFmArtist>() name: String,
lang: String?,
fun getArtist(): LiveData<Artist> = _artist cache: String?
fun getArtistInfo(): LiveData<LastFmArtist> = _lastFmArtist ): LiveData<Result<LastFmArtist>> = liveData(IO) {
emit(Result.Loading)
init {
loadArtistDetails()
}
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 {
val info = realRepository.artistInfo(name, lang, cache) val info = realRepository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info) emit(info)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadArtistDetails() getArtist()
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View file

@ -1,7 +1,7 @@
package io.github.muntashirakon.music.fragments.artists package io.github.muntashirakon.music.fragments.artists
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.*
import android.widget.ImageView import android.widget.ImageView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Observer 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.extensions.findActivityNavController
import io.github.muntashirakon.music.fragments.ReloadType import io.github.muntashirakon.music.fragments.ReloadType
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment 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.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.transition.platform.MaterialFadeThrough
class ArtistsFragment :
AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
MainActivityFragmentCallbacks, ArtistClickListener {
override fun handleBackPress(): Boolean { class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
return false ArtistClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.artistsLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else
@ -50,7 +51,7 @@ class ArtistsFragment :
return ArtistAdapter( return ArtistAdapter(
requireActivity(), requireActivity(),
dataSet, dataSet,
R.layout.item_grid_circle, itemLayoutRes(),
null, null,
this 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) val controller = findActivityNavController(R.id.fragment_container)
controller.navigate(R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId)) 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 { interface ArtistClickListener {
fun onArtist(artistId: Int, imageView: ImageView) fun onArtist(artistId: Long, imageView: ImageView)
} }

View file

@ -1,10 +1,9 @@
package io.github.muntashirakon.music.fragments.base package io.github.muntashirakon.music.fragments.base
import android.annotation.SuppressLint
import android.content.ContentUris import android.content.ContentUris
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.os.AsyncTask
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
@ -15,30 +14,41 @@ import android.widget.Toast
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import io.github.muntashirakon.music.EXTRA_ALBUM_ID import io.github.muntashirakon.music.EXTRA_ALBUM_ID
import io.github.muntashirakon.music.EXTRA_ARTIST_ID import io.github.muntashirakon.music.EXTRA_ARTIST_ID
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.tageditor.AbsTagEditorActivity import io.github.muntashirakon.music.activities.tageditor.AbsTagEditorActivity
import io.github.muntashirakon.music.activities.tageditor.SongTagEditorActivity 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.dialogs.*
import io.github.muntashirakon.music.extensions.hide 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.LibraryViewModel
import io.github.muntashirakon.music.fragments.ReloadType
import io.github.muntashirakon.music.fragments.player.PlayerAlbumCoverFragment import io.github.muntashirakon.music.fragments.player.PlayerAlbumCoverFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.PaletteColorHolder import io.github.muntashirakon.music.interfaces.PaletteColorHolder
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.model.lyrics.Lyrics 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 io.github.muntashirakon.music.util.*
import kotlinx.android.synthetic.main.shadow_statusbar_toolbar.* 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 org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.io.FileNotFoundException import java.io.FileNotFoundException
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout), abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
Toolbar.OnMenuItemClickListener, PaletteColorHolder, PlayerAlbumCoverFragment.Callbacks { Toolbar.OnMenuItemClickListener, PaletteColorHolder, PlayerAlbumCoverFragment.Callbacks {
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private var updateLyricsAsyncTask: AsyncTask<*, *, *>? = null
private var playerAlbumCoverFragment: PlayerAlbumCoverFragment? = null private var playerAlbumCoverFragment: PlayerAlbumCoverFragment? = null
protected val libraryViewModel by sharedViewModel<LibraryViewModel>() protected val libraryViewModel by sharedViewModel<LibraryViewModel>()
@ -64,7 +74,13 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true return true
} }
R.id.action_add_to_playlist -> { R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(song).show(childFragmentManager, "ADD_PLAYLIST") lifecycleScope.launch(IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Main) {
AddToPlaylistDialog.create(playlists, song)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true return true
} }
R.id.action_clear_playing_queue -> { R.id.action_clear_playing_queue -> {
@ -146,9 +162,6 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return false return false
} }
protected open fun toggleFavorite(song: Song) {
MusicUtil.toggleFavorite(requireActivity(), song)
}
abstract fun playerToolbar(): Toolbar? abstract fun playerToolbar(): Toolbar?
@ -170,79 +183,70 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
updateLyrics() updateLyrics()
} }
override fun onDestroyView() { protected open fun toggleFavorite(song: Song) {
if (updateIsFavoriteTask != null && !updateIsFavoriteTask!!.isCancelled) { lifecycleScope.launch(IO) {
updateIsFavoriteTask!!.cancel(true) 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)))
} }
if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask!!.isCancelled) {
updateLyricsAsyncTask!!.cancel(true)
} }
super.onDestroyView() libraryViewModel.forceReload(ReloadType.Playlists)
requireContext().sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
} }
@SuppressLint("StaticFieldLeak")
fun updateIsFavorite() { fun updateIsFavorite() {
if (updateIsFavoriteTask != null) { lifecycleScope.launch(IO) {
updateIsFavoriteTask!!.cancel(false) val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist()
} if (playlist != null) {
updateIsFavoriteTask = object : AsyncTask<Song, Void, Boolean>() { val song: SongEntity =
override fun doInBackground(vararg params: Song): Boolean { MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
return MusicUtil.isFavorite(requireActivity(), params[0]) val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
} withContext(Main) {
val icon =
override fun onPostExecute(isFavorite: Boolean) { if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
val res = if (isFavorite) val drawable: Drawable? = RetroUtil.getTintedVectorDrawable(
R.drawable.ic_favorite requireContext(),
else icon,
R.drawable.ic_favorite_border toolbarIconColor()
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
) )
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)
}
}
}
} }
}.execute(MusicPlayerRemote.currentSong)
} }
@SuppressLint("StaticFieldLeak")
private fun updateLyrics() { private fun updateLyrics() {
if (updateLyricsAsyncTask != null) updateLyricsAsyncTask!!.cancel(false)
updateLyricsAsyncTask = object : AsyncTask<Song, Void, Lyrics>() {
override fun onPreExecute() {
super.onPreExecute()
setLyrics(null) setLyrics(null)
} lifecycleScope.launch(IO) {
val song = MusicPlayerRemote.currentSong
override fun doInBackground(vararg params: Song): Lyrics? { val lyrics = try {
try { var data: String? = LyricUtil.getStringFromFile(song.title, song.artistName)
var data: String? = if (TextUtils.isEmpty(data)) {
LyricUtil.getStringFromFile(params[0].title, params[0].artistName) data = MusicUtil.getLyrics(song)
return if (TextUtils.isEmpty(data)) { if (TextUtils.isEmpty(data)) {
data = MusicUtil.getLyrics(params[0])
return if (TextUtils.isEmpty(data)) {
null null
} else { } else {
Lyrics.parse(params[0], data) Lyrics.parse(song, data)
} }
} else Lyrics.parse(params[0], data!!) } else Lyrics.parse(song, data!!)
} catch (err: FileNotFoundException) { } catch (err: FileNotFoundException) {
return null null
}
withContext(Main) {
setLyrics(lyrics)
} }
} }
override fun onPostExecute(l: Lyrics?) {
setLyrics(l)
}
override fun onCancelled(s: Lyrics?) {
onPostExecute(null)
}
}.execute(MusicPlayerRemote.currentSong)
} }
open fun setLyrics(l: Lyrics?) { open fun setLyrics(l: Lyrics?) {
@ -255,8 +259,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
) { ) {
view.findViewById<View>(R.id.status_bar).visibility = View.GONE view.findViewById<View>(R.id.status_bar).visibility = View.GONE
} }
playerAlbumCoverFragment = playerAlbumCoverFragment = whichFragment(R.id.playerAlbumCoverFragment)
childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment?
playerAlbumCoverFragment?.setCallbacks(this) playerAlbumCoverFragment?.setCallbacks(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)

View file

@ -79,7 +79,6 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
} }
private fun checkIsEmpty() { private fun checkIsEmpty() {
emptyEmoji.text = getEmojiByUnicode(0x1F631)
emptyText.setText(emptyMessage) emptyText.setText(emptyMessage)
empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
} }

View file

@ -36,7 +36,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
setupRecyclerView() setupRecyclerView()
detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer { detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer {
songs(it) songs(it)

View file

@ -21,18 +21,17 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.GenreAdapter import io.github.muntashirakon.music.adapter.GenreAdapter
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment 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<GenreAdapter, LinearLayoutManager>(), class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>() {
MainActivityFragmentCallbacks { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
override fun handleBackPress(): Boolean { enterTransition = MaterialFadeThrough()
return false
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.genresLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else

View file

@ -16,50 +16,41 @@ package io.github.muntashirakon.music.fragments.home
import android.app.ActivityOptions import android.app.ActivityOptions
import android.os.Bundle 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 android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide import io.github.muntashirakon.music.HISTORY_PLAYLIST
import io.github.muntashirakon.music.EXTRA_PLAYLIST import io.github.muntashirakon.music.LAST_ADDED_PLAYLIST
import io.github.muntashirakon.music.R 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.adapter.HomeAdapter
import io.github.muntashirakon.music.extensions.findActivityNavController import io.github.muntashirakon.music.extensions.findActivityNavController
import io.github.muntashirakon.music.fragments.LibraryViewModel import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.glide.ProfileBannerGlideRequest import io.github.muntashirakon.music.glide.ProfileBannerGlideRequest
import io.github.muntashirakon.music.glide.UserProfileGlideRequest 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.NavigationUtil
import io.github.muntashirakon.music.util.PreferenceUtil 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.abs_playlists.*
import kotlinx.android.synthetic.main.fragment_banner_home.* import kotlinx.android.synthetic.main.fragment_banner_home.*
import kotlinx.android.synthetic.main.home_content.* 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 import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class HomeFragment : class HomeFragment :
AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home) { AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home) {
override fun onCreate(savedInstanceState: Bundle?) {
private val repository by inject<Repository>() super.onCreate(savedInstanceState)
private val libraryViewModel: LibraryViewModel by sharedViewModel() enterTransition = MaterialFadeThrough()
private val displayMetrics: DisplayMetrics
get() {
val display = mainActivity.windowManager.defaultDisplay
val metrics = DisplayMetrics()
display.getMetrics(metrics)
return metrics
} }
private val libraryViewModel: LibraryViewModel by sharedViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setStatusBarColorAuto(view) setStatusBarColorAuto(view)
@ -74,31 +65,26 @@ class HomeFragment :
lastAdded.setOnClickListener { lastAdded.setOnClickListener {
findActivityNavController(R.id.fragment_container).navigate( findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment, R.id.detailListFragment,
bundleOf(EXTRA_PLAYLIST to LastAddedPlaylist()) bundleOf("type" to LAST_ADDED_PLAYLIST)
) )
} }
topPlayed.setOnClickListener { topPlayed.setOnClickListener {
findActivityNavController(R.id.fragment_container).navigate( findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment, R.id.detailListFragment,
bundleOf(EXTRA_PLAYLIST to TopTracksPlaylist()) bundleOf("type" to TOP_PLAYED_PLAYLIST)
) )
} }
actionShuffle.setOnClickListener { actionShuffle.setOnClickListener {
lifecycleScope.launch { libraryViewModel.shuffleSongs()
MusicPlayerRemote.openAndShuffleQueue(
repository.allSongs(),
true
)
}
} }
history.setOnClickListener { history.setOnClickListener {
requireActivity().findNavController(R.id.fragment_container).navigate( findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment, R.id.detailListFragment,
bundleOf(EXTRA_PLAYLIST to HistoryPlaylist()) bundleOf("type" to HISTORY_PLAYLIST)
) )
} }
@ -118,7 +104,7 @@ class HomeFragment :
adapter = homeAdapter adapter = homeAdapter
} }
libraryViewModel.homeLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getHome().observe(viewLifecycleOwner, Observer {
homeAdapter.swapData(it) homeAdapter.swapData(it)
}) })
@ -138,6 +124,14 @@ class HomeFragment :
).build().into(userImage) ).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 { companion object {
const val TAG: String = "BannerHomeFragment" const val TAG: String = "BannerHomeFragment"

View file

@ -4,21 +4,26 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.text.HtmlCompat
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper 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.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.extensions.findNavController
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import kotlinx.android.synthetic.main.fragment_library.* import kotlinx.android.synthetic.main.fragment_library.*
import java.lang.String
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
retainInstance = true
mainActivity.hideBottomBarVisibility(true) mainActivity.hideBottomBarVisibility(true)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
mainActivity.supportActionBar?.title = null mainActivity.supportActionBar?.title = null
@ -30,6 +35,17 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
) )
} }
setupNavigationController() setupNavigationController()
setupTitle()
}
private fun setupTitle() {
val color = ThemeStore.accentColor(requireContext())
val hexColor = String.format("#%06X", 0xFFFFFF and color)
val appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
} }
private fun setupNavigationController() { private fun setupNavigationController() {
@ -60,19 +76,15 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
null, null,
navOptions 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) 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
}
} }

View file

@ -20,7 +20,6 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.RetroBottomSheetBehavior 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.adapter.song.PlayingQueueAdapter
import io.github.muntashirakon.music.extensions.hide import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.extensions.show 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() { private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
playerQueueSheet.setContentPadding( playerQueueSheet.setContentPadding(
playerQueueSheet.contentPaddingLeft, playerQueueSheet.contentPaddingLeft,
(slideOffset * status_bar.height).toInt(), (slideOffset * status_bar.height).toInt(),
@ -83,18 +80,17 @@ class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) { when (newState) {
BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> { BottomSheetBehavior.STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false) mainActivity.getBottomSheetBehavior().setAllowDragging(false)
} }
BottomSheetBehavior.STATE_COLLAPSED -> { BottomSheetBehavior.STATE_COLLAPSED -> {
resetToCurrentPosition() resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true) mainActivity.getBottomSheetBehavior().setAllowDragging(true)
} }
else -> { 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.background = shapeDrawable
playerQueueSheet.setOnTouchListener { _, _ -> playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
getQueuePanel().setAllowDragging(true) getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false return@setOnTouchListener false
} }

View file

@ -3,9 +3,7 @@ package io.github.muntashirakon.music.fragments.player.fit
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator 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.misc.SimpleOnSeekbarChangeListener
import io.github.muntashirakon.music.service.MusicService import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.* import kotlinx.android.synthetic.main.fragment_fit_playback_controls.*

View file

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
@ -15,14 +14,16 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil 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.R
import io.github.muntashirakon.music.RetroBottomSheetBehavior 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.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.hide
import io.github.muntashirakon.music.extensions.ripAlpha import io.github.muntashirakon.music.extensions.ripAlpha
import io.github.muntashirakon.music.extensions.show 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.PreferenceUtil
import io.github.muntashirakon.music.util.ViewUtil import io.github.muntashirakon.music.util.ViewUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor 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.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager 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_controls.*
import kotlinx.android.synthetic.main.fragment_gradient_player.* import kotlinx.android.synthetic.main.fragment_gradient_player.*
import kotlinx.android.synthetic.main.status_bar.* 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), class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_player),
MusicProgressViewUpdateHelper.Callback, MusicProgressViewUpdateHelper.Callback,
@ -62,14 +66,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null private var playingQueueAdapter: PlayingQueueAdapter? = null
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var linearLayoutManager: LinearLayoutManager
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
playerQueueSheet.setPadding( playerQueueSheet.setPadding(
playerQueueSheet.paddingLeft, playerQueueSheet.paddingLeft,
(slideOffset * status_bar.height).toInt(), (slideOffset * status_bar.height).toInt(),
@ -79,18 +80,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) { when (newState) {
BottomSheetBehavior.STATE_EXPANDED, STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> { STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false) mainActivity.getBottomSheetBehavior().setAllowDragging(false)
} }
BottomSheetBehavior.STATE_COLLAPSED -> { STATE_COLLAPSED -> {
resetToCurrentPosition() resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true) mainActivity.getBottomSheetBehavior().setAllowDragging(true)
} }
else -> { else -> {
activity.getBottomSheetBehavior().setAllowDragging(true) mainActivity.getBottomSheetBehavior().setAllowDragging(true)
} }
} }
} }
@ -139,8 +139,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun setupSheet() { private fun setupSheet() {
getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList) getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList)
playerQueueSheet.setOnTouchListener { _, _ -> playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
getQueuePanel().setAllowDragging(true) getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false return@setOnTouchListener false
} }
@ -159,7 +158,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
recyclerViewDragDropManager?.cancelDrag() recyclerViewDragDropManager?.cancelDrag()
super.onPause() super.onPause()
progressViewUpdateHelper.stop() progressViewUpdateHelper.stop()
} }
override fun playerToolbar(): Toolbar? { override fun playerToolbar(): Toolbar? {
@ -176,9 +174,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
var wasExpanded = false var wasExpanded = false
if (getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED) { if (getQueuePanel().state == STATE_EXPANDED) {
wasExpanded = getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED wasExpanded = getQueuePanel().state == STATE_EXPANDED
getQueuePanel().state = BottomSheetBehavior.STATE_COLLAPSED getQueuePanel().state = STATE_COLLAPSED
return wasExpanded return wasExpanded
} }
return wasExpanded return wasExpanded
@ -224,9 +222,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun toggleFavorite(song: Song) { override fun toggleFavorite(song: Song) {
super.toggleFavorite(song) super.toggleFavorite(song)
MusicUtil.toggleFavorite(requireContext(), song)
if (song.id == MusicPlayerRemote.currentSong.id) { if (song.id == MusicPlayerRemote.currentSong.id) {
updateFavorite() updateIsFavoriteIcon()
} }
} }
@ -234,6 +231,23 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
toggleFavorite(MusicPlayerRemote.currentSong) 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() { private fun hideVolumeIfAvailable() {
if (PreferenceUtil.isVolumeVisibilityMode) { if (PreferenceUtil.isVolumeVisibilityMode) {
childFragmentManager.beginTransaction() childFragmentManager.beginTransaction()
@ -251,6 +265,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
updatePlayPauseDrawableState() updatePlayPauseDrawableState()
updatePlayPauseDrawableState() updatePlayPauseDrawableState()
updateQueue() updateQueue()
updateIsFavoriteIcon()
} }
override fun onPlayStateChanged() { override fun onPlayStateChanged() {
@ -269,11 +284,13 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateSong() updateSong()
updateQueuePosition() updateQueuePosition()
updateIsFavoriteIcon()
} }
override fun onQueueChanged() { override fun onQueueChanged() {
super.onQueueChanged() super.onQueueChanged()
updateLabel() updateLabel()
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue)
} }
private fun updateSong() { private fun updateSong() {
@ -368,13 +385,10 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun updateLabel() { private fun updateLabel() {
(MusicPlayerRemote.playingQueue.size - 1).apply { (MusicPlayerRemote.playingQueue.size - 1).apply {
if (this == (MusicPlayerRemote.position)) { if (this == (MusicPlayerRemote.position)) {
nextSong.hide() nextSong.text = "Last song"
} else { } else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
nextSong.apply { nextSong.text = title
text = "Next: $title"
show()
}
} }
} }
} }
@ -469,45 +483,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator() animator.interpolator = LinearInterpolator()
animator.start() animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
@SuppressLint("StaticFieldLeak")
private fun updateFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask?.cancel(false)
}
updateIsFavoriteTask =
object : AsyncTask<Song, Void, Boolean>() {
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)
}
} }

View file

@ -5,23 +5,22 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.lifecycle.Observer
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.R
import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter 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.extensions.dipToPix
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper 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.model.Song
import io.github.muntashirakon.music.util.PlaylistsUtil 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 kotlinx.android.synthetic.main.fragment_playlist_detail.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@ -32,7 +31,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
parametersOf(arguments.extraPlaylist) parametersOf(arguments.extraPlaylist)
} }
private lateinit var playlist: Playlist private lateinit var playlist: PlaylistWithSongs
private lateinit var adapter: SongAdapter private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null private var wrappedAdapter: RecyclerView.Adapter<*>? = null
@ -46,28 +45,22 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
playlist = arguments.extraPlaylist playlist = arguments.extraPlaylist
toolbar.title = playlist.playlistEntity.playlistName
setUpRecyclerView() setUpRecyclerView()
viewModel.getSongs().observe(viewLifecycleOwner, Observer { viewModel.getSongs().observe(viewLifecycleOwner, {
songs(it) songs(it.toSongs())
})
viewModel.getPlaylist().observe(viewLifecycleOwner, Observer {
playlist = it
toolbar.title = it.name
}) })
} }
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
if (playlist is AbsCustomPlaylist) {
adapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null)
recyclerView.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager() recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator() val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter( adapter =
OrderablePlaylistSongAdapter(
playlist.playlistEntity,
requireActivity(), requireActivity(),
ArrayList(), ArrayList(),
R.layout.item_list, R.layout.item_list,
@ -76,7 +69,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onMoveItem(fromPosition: Int, toPosition: Int) { override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem( if (PlaylistsUtil.moveItem(
requireContext(), requireContext(),
playlist.id, playlist.playlistEntity.playListId,
fromPosition, fromPosition,
toPosition toPosition
) )
@ -93,7 +86,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
recyclerView.itemAnimator = animator recyclerView.itemAnimator = animator
recyclerViewDragDropManager?.attachRecyclerView(recyclerView) recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
super.onChanged() super.onChanged()
@ -104,9 +97,9 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
val menuRes = if (playlist is AbsCustomPlaylist) val menuRes =/* if (playlist is AbsCustomPlaylist)
R.menu.menu_smart_playlist_detail R.menu.menu_smart_playlist_detail
else R.menu.menu_playlist_detail else*/ R.menu.menu_playlist_detail
inflater.inflate(menuRes, menu) inflater.inflate(menuRes, menu)
} }
@ -121,15 +114,10 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
private fun checkIsEmpty() { private fun checkIsEmpty() {
checkForPadding() checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
emptyText.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() { override fun onPause() {
if (recyclerViewDragDropManager != null) { if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag() recyclerViewDragDropManager!!.cancelDrag()
@ -161,11 +149,11 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
} }
fun songs(songs: List<Song>) { fun songs(songs: List<Song>) {
progressIndicator.hide()
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
adapter.swapDataSet(songs) adapter.swapDataSet(songs)
} else { } else {
showEmptyView() showEmptyView()
} }
} }
} }

View file

@ -3,42 +3,29 @@ package io.github.muntashirakon.music.fragments.playlists
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import io.github.muntashirakon.music.db.PlaylistWithSongs
import io.github.muntashirakon.music.App import io.github.muntashirakon.music.db.SongEntity
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener 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.model.Song
import io.github.muntashirakon.music.repository.RealRepository 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( class PlaylistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private var playlist: Playlist private var playlist: PlaylistWithSongs
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val _playListSongs = MutableLiveData<List<Song>>() private val _playListSongs = MutableLiveData<List<Song>>()
private val _playlist = MutableLiveData<Playlist>().apply { private val _playlist = MutableLiveData<PlaylistWithSongs>().apply {
postValue(playlist) postValue(playlist)
} }
fun getPlaylist(): LiveData<Playlist> = _playlist fun getPlaylist(): LiveData<PlaylistWithSongs> = _playlist
fun getSongs(): LiveData<List<Song>> = _playListSongs fun getSongs(): LiveData<List<SongEntity>> = 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() { override fun onMediaStoreChanged() {
if (playlist !is AbsCustomPlaylist) { /*if (playlist !is AbsCustomPlaylist) {
// Playlist deleted // Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) { if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) {
//TODO Finish the page //TODO Finish the page
@ -54,7 +41,7 @@ class PlaylistDetailsViewModel(
} }
} }
} }
loadPlaylistSongs(playlist) loadPlaylistSongs(playlist)*/
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View file

@ -1,25 +1,28 @@
package io.github.muntashirakon.music.fragments.playlists package io.github.muntashirakon.music.fragments.playlists
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import androidx.lifecycle.Observer 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.R
import io.github.muntashirakon.music.adapter.playlist.PlaylistAdapter import io.github.muntashirakon.music.adapter.playlist.PlaylistAdapter
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment 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 : class PlaylistsFragment : AbsRecyclerViewFragment<PlaylistAdapter, LinearLayoutManager>() {
AbsRecyclerViewFragment<PlaylistAdapter, GridLayoutManager>(), override fun onCreate(savedInstanceState: Bundle?) {
MainActivityFragmentCallbacks { super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
override fun handleBackPress(): Boolean {
return false
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.playlisitsLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getPlaylists().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else
@ -30,8 +33,8 @@ class PlaylistsFragment :
override val emptyMessage: Int override val emptyMessage: Int
get() = R.string.no_playlists get() = R.string.no_playlists
override fun createLayoutManager(): GridLayoutManager { override fun createLayoutManager(): LinearLayoutManager {
return GridLayoutManager(requireContext(), 1) return LinearLayoutManager(requireContext())
} }
override fun createAdapter(): PlaylistAdapter { override fun createAdapter(): PlaylistAdapter {
@ -43,9 +46,18 @@ class PlaylistsFragment :
) )
} }
companion object { override fun onPrepareOptionsMenu(menu: Menu) {
fun newInstance(): PlaylistsFragment { super.onPrepareOptionsMenu(menu)
return PlaylistsFragment() 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)
} }
} }

View file

@ -22,7 +22,6 @@ import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote 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.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager 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. * Created by hemanths on 2019-12-08.
*/ */
class PlayingQueueFragment : class PlayingQueueFragment : AbsRecyclerViewFragment<PlayingQueueAdapter, LinearLayoutManager>() {
AbsRecyclerViewFragment<PlayingQueueAdapter, LinearLayoutManager>(),
MainActivityFragmentCallbacks {
override fun handleBackPress(): Boolean {
return false
}
private lateinit var wrappedAdapter: RecyclerView.Adapter<*> private lateinit var wrappedAdapter: RecyclerView.Adapter<*>
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null

View file

@ -6,9 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.github.muntashirakon.music.repository.RealRepository import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SearchViewModel(private val realRepository: RealRepository) : ViewModel() { class SearchViewModel(private val realRepository: RealRepository) : ViewModel() {
private val results = MutableLiveData<MutableList<Any>>() private val results = MutableLiveData<MutableList<Any>>()
@ -17,6 +15,6 @@ class SearchViewModel(private val realRepository: RealRepository) : ViewModel()
fun search(query: String?) = viewModelScope.launch(IO) { fun search(query: String?) = viewModelScope.launch(IO) {
val result = realRepository.search(query) val result = realRepository.search(query)
withContext(Main) { results.postValue(result) } results.postValue(result)
} }
} }

View file

@ -14,13 +14,19 @@
package io.github.muntashirakon.music.fragments.settings package io.github.muntashirakon.music.fragments.settings
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController 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.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.* import kotlinx.android.synthetic.main.fragment_main_settings.*
class MainSettingsFragment : Fragment(), View.OnClickListener { class MainSettingsFragment : Fragment(), View.OnClickListener {

Some files were not shown because too many files have changed in this diff Show more