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
app/normal/release/
/models/
app/font/
app/src/debug/
/app/nofont/
/crowdin.properties
/app/release/

View file

@ -2,20 +2,11 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "ru.cleverpumpkin.proguard-dictionaries-generator"
apply plugin: "androidx.navigation.safeargs.kotlin"
proguardDictionaries {
dictionaryNames = [
"build/class-dictionary",
"build/package-dictionary",
"build/obfuscation-dictionary"
]
}
android {
compileSdkVersion 29
buildToolsVersion = '30.0.0'
buildToolsVersion = '30.0.1'
defaultConfig {
minSdkVersion 21
@ -25,7 +16,7 @@ android {
vectorDrawables.useSupportLibrary = true
applicationId 'io.github.muntashirakon.Music'
versionCode 10443
versionCode 10444
versionName '3.5.10'
multiDexEnabled true
@ -73,66 +64,51 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
def material_dialog_version = "0.9.6.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') {
transitive = true
}
def kotlin_coroutines_version = "1.3.8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'com.google.android.material:material:1.3.0-alpha01'
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
def material_dialog_version = "0.9.6.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
def kotlin_coroutines_version = "1.3.8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version"
@ -142,7 +118,22 @@ dependencies {
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
implementation 'org.jsoup:jsoup:1.11.1'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

View file

@ -41,16 +41,10 @@
public *;
}
-keep class !android.support.v7.internal.view.menu.**,** {*;}
-dontwarn
-ignorewarnings
-keep public class android.support.design.widget.BottomNavigationView { *; }
-keep public class android.support.design.internal.BottomNavigationMenuView { *; }
-keep public class android.support.design.internal.BottomNavigationPresenter { *; }
-keep public class android.support.design.internal.BottomNavigationItemView { *; }
#-dontwarn android.support.v8.renderscript.*
#-keepclassmembers class android.support.v8.renderscript.RenderScript {
# native *** rsn*(...);
@ -58,8 +52,3 @@
#}
#-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.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" />
<activity android:name=".activities.PermissionActivity" />
<activity
android:name=".activities.LockScreenActivity"
@ -252,11 +253,9 @@
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

View file

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

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 FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
const val BASE_URL = "https://ws.audioscrobbler.com/2.0/"
const val AUDIO_SCROBBLER_URL = "https://ws.audioscrobbler.com/2.0/"
const val IS_MUSIC =
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
@ -55,9 +55,11 @@ object Constants {
const val EXTRA_GENRE = "extra_genre"
const val EXTRA_PLAYLIST = "extra_playlist"
const val EXTRA_PLAYLIST_ID = "extra_playlist_id"
const val EXTRA_ALBUM_ID = "extra_album_id"
const val EXTRA_ARTIST_ID = "extra_artist_id"
const val EXTRA_SONG = "extra_songs"
const val EXTRA_PLAYLISTS = "extra_playlists"
const val LIBRARY_CATEGORIES = "library_categories"
const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color"
@ -68,8 +70,8 @@ const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification"
const val CLASSIC_NOTIFICATION = "classic_notification"
const val GAPLESS_PLAYBACK = "gapless_playback"
const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen"
const val GAP_LESS_PLAYBACK = "gap_less_playback"
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount"
const val TOGGLE_HEADSET = "toggle_headset"
@ -90,7 +92,6 @@ const val ALBUM_COVER_STYLE = "album_cover_style_id"
const val ALBUM_COVER_TRANSFORM = "album_cover_transform"
const val TAB_TEXT_MODE = "tab_text_mode"
const val LANGUAGE_NAME = "language_name"
const val DIALOG_CORNER = "dialog_corner"
const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
const val ALBUM_GRID_STYLE = "album_grid_style_home"
const val ARTIST_GRID_STYLE = "artist_grid_style_home"

View file

@ -23,3 +23,6 @@ const val SUGGESTIONS = 5
const val FAVOURITES = 4
const val GENRES = 6
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
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import code.name.monkey.retromusic.db.BlackListStoreDao
import code.name.monkey.retromusic.db.BlackListStoreEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.RetroDatabase
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.albums.AlbumDetailsViewModel
import io.github.muntashirakon.music.fragments.artists.ArtistDetailsViewModel
@ -7,17 +14,103 @@ import io.github.muntashirakon.music.fragments.genres.GenreDetailsViewModel
import io.github.muntashirakon.music.fragments.playlists.PlaylistDetailsViewModel
import io.github.muntashirakon.music.fragments.search.SearchViewModel
import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.network.networkModule
import io.github.muntashirakon.music.network.*
import io.github.muntashirakon.music.repository.*
import io.github.muntashirakon.music.util.FilePathUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind
import org.koin.dsl.module
val networkModule = module {
factory {
provideDefaultCache()
}
factory {
provideOkHttp(get(), get())
}
single {
provideLastFmRetrofit(get())
}
single {
provideDeezerRest(get())
}
single {
provideLastFmRest(get())
}
single {
provideLyrics(get())
}
}
private val roomModule = module {
single {
Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
.allowMainThreadQueries()
.addCallback(object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
GlobalScope.launch(IO) {
FilePathUtil.blacklistFilePaths().map {
get<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 {
single {
RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
RealRepository(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
} bind Repository::class
single {
@ -61,10 +154,6 @@ private val dataModule = module {
get()
)
}
single {
androidContext().contentResolver
}
}
private val viewModules = module {
@ -73,21 +162,21 @@ private val viewModules = module {
LibraryViewModel(get())
}
viewModel { (albumId: Int) ->
viewModel { (albumId: Long) ->
AlbumDetailsViewModel(
get(),
albumId
)
}
viewModel { (artistId: Int) ->
viewModel { (artistId: Long) ->
ArtistDetailsViewModel(
get(),
artistId
)
}
viewModel { (playlist: Playlist) ->
viewModel { (playlist: PlaylistWithSongs) ->
PlaylistDetailsViewModel(
get(),
playlist
@ -106,4 +195,4 @@ private val viewModules = module {
}
}
val appModules = listOf(dataModule, viewModules, networkModule)
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)

View file

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

View file

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

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() {

View file

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

View file

@ -4,17 +4,23 @@ import android.Manifest
import android.content.*
import android.os.Bundle
import android.os.IBinder
import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.toPlayCount
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.repository.RealRepository
import io.github.muntashirakon.music.service.MusicService.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>()
private val repository: RealRepository by inject()
private var serviceToken: MusicPlayerRemote.ServiceToken? = null
private var musicStateReceiver: MusicStateReceiver? = null
private var receiverRegistered: Boolean = false
@ -93,6 +99,22 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
for (listener in mMusicServiceEventListeners) {
listener.onPlayingMetaChanged()
}
lifecycleScope.launch(Dispatchers.IO) {
val entity = repository.songPresentInHistory(MusicPlayerRemote.currentSong)
if (entity != null) {
repository.updateHistorySong(MusicPlayerRemote.currentSong)
} else {
repository.addSongToHistory(MusicPlayerRemote.currentSong)
}
val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
if (songs.isNotEmpty()) {
repository.updateSongInPlayCount(songs.first().apply {
playCount += 1
})
} else {
repository.insertSongInPlayCount(MusicPlayerRemote.currentSong.toPlayCount())
}
}
}
override fun onQueueChanged() {

View file

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

View file

@ -87,7 +87,7 @@ public class DeviceInfo {
return "Device info:\n"
+ "---\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>Android build version</td><td>" + buildVersion + "</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.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
@ -41,7 +40,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton
protected var id: Int = 0
protected var id: Long = 0
private set
private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
@ -182,12 +181,10 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
saveFab = findViewById(R.id.saveTags)
getIntentExtras()
lifecycleScope.launchWhenCreated {
songPaths = getSongPaths()
if (songPaths!!.isEmpty()) {
finish()
}
}
setUpViews()
}
@ -254,11 +251,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
private fun getIntentExtras() {
val intentExtras = intent.extras
if (intentExtras != null) {
id = intentExtras.getInt(EXTRA_ID)
id = intentExtras.getLong(EXTRA_ID)
}
}
protected abstract suspend fun getSongPaths(): List<String>
protected abstract fun getSongPaths(): List<String>
protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder()
@ -399,7 +396,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
}
}
class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
class ArtworkInfo constructor(val albumId: Long, val artwork: Bitmap?)
companion object {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -4,15 +4,6 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
import com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying
@ -21,6 +12,14 @@ import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.ViewUtil
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter(
@ -153,8 +152,8 @@ class PlayingQueueAdapter(
mDragStateFlags = flags
}
override fun getSwipeableContainerView(): View? {
return dummyContainer
override fun getSwipeableContainerView(): View {
return dummyContainer!!
}
}
@ -165,18 +164,15 @@ class PlayingQueueAdapter(
private const val UP_NEXT = 2
}
override fun onSwipeItem(
holder: ViewHolder?,
position: Int, @SwipeableItemResults result: Int
): SwipeResultAction {
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? {
return if (result == SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault()
} else {
SwipedResultActionRemoveItem(this, position, activity)
}
}
override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int {
override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int {
return if (onCheckCanStartDrag(holder!!, position, x, y)) {
SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
} else {
@ -184,10 +180,10 @@ class PlayingQueueAdapter(
}
}
override fun onSwipeItemStarted(p0: ViewHolder?, p1: Int) {
override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) {
}
override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) {
override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) {
}
internal class SwipedResultActionRemoveItem(

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
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.EXTRA_PLAYLISTS
import io.github.muntashirakon.music.EXTRA_SONG
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.SongEntity
import io.github.muntashirakon.music.db.toSongsEntity
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.ReloadType.Playlists
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.PlaylistRepository
import io.github.muntashirakon.music.util.PlaylistsUtil
import org.koin.android.ext.android.inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AddToPlaylistDialog : DialogFragment() {
private val playlistRepository by inject<PlaylistRepository>()
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val playlists = playlistRepository.playlists()
val playlistNames = mutableListOf<String>()
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object {
fun create(playlistEntities: List<PlaylistEntity>, song: Song): AddToPlaylistDialog {
val list: MutableList<Song> = mutableListOf()
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))
for (p in playlists) {
playlistNames.add(p.name)
for (entity: PlaylistEntity in playlistEntities) {
playlistNames.add(entity.playlistName)
}
return materialDialog(R.string.add_playlist_title)
.setItems(playlistNames.toTypedArray()) { _, which ->
val songs = extraNotNull<ArrayList<Song>>(EXTRA_SONG).value
if (which == 0) {
CreatePlaylistDialog.create(songs)
.show(requireActivity().supportFragmentManager, "ADD_TO_PLAYLIST")
.show(requireActivity().supportFragmentManager, "Dialog")
} else {
PlaylistsUtil.addToPlaylist(
requireContext(),
songs,
playlists[which - 1].id,
true
)
lifecycleScope.launch(Dispatchers.IO) {
val songEntities: List<SongEntity> =
songs.toSongsEntity(playlistEntities[which - 1])
libraryViewModel.insertSongs(songEntities)
libraryViewModel.forceReload(Playlists)
}
}
dismiss()
}
.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
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil
import androidx.lifecycle.lifecycleScope
import io.github.muntashirakon.music.EXTRA_SONG
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.extra
import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.ReloadType.Playlists
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.PlaylistsUtil
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class CreatePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams")
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
companion object {
fun create(song: Song): CreatePlaylistDialog {
val list = mutableListOf<Song>()
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 songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList()
val playlistView: TextInputEditText = view.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer
MaterialUtil.setTint(playlistContainer, false)
return materialDialog(R.string.new_playlist_title)
.setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(
R.string.create_action
) { _, _ ->
val extra = extraNotNull<ArrayList<Song>>(EXTRA_SONG)
val playlistName = playlistView.text.toString()
if (!TextUtils.isEmpty(playlistName)) {
val playlistId = PlaylistsUtil.createPlaylist(
requireContext(),
playlistView.text.toString()
)
if (playlistId != -1) {
PlaylistsUtil.addToPlaylist(requireContext(), extra.value, playlistId, true)
lifecycleScope.launch(Dispatchers.IO) {
if (libraryViewModel.checkPlaylistExists(playlistName).isEmpty()) {
val playlistId: Long =
libraryViewModel.createPlaylist(PlaylistEntity(playlistName = playlistName))
libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId) })
libraryViewModel.forceReload(Playlists)
} else {
Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT)
.show()
}
}
} else {
playlistContainer.error = "Playlist is can't be empty"
}
}
.create()
.colorButtons()
}
companion object {
@JvmOverloads
@JvmStatic
fun create(song: Song? = null): CreatePlaylistDialog {
val list = ArrayList<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
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import io.github.muntashirakon.music.EXTRA_PLAYLIST
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.util.PlaylistsUtil
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.ReloadType
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeletePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<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 {
val playlists = extraNotNull<List<Playlist>>(EXTRA_PLAYLIST).value
val playlists = extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLIST).value
val title: Int
val message: CharSequence
//noinspection ConstantConditions
@ -42,7 +48,7 @@ class DeletePlaylistDialog : DialogFragment() {
} else {
title = R.string.delete_playlist_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_playlist_x), playlists[0].name),
String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
@ -52,26 +58,12 @@ class DeletePlaylistDialog : DialogFragment() {
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ ->
PlaylistsUtil.deletePlaylists(requireContext(), playlists)
libraryViewModel.deleteSongsFromPlaylist(playlists)
libraryViewModel.deleteRoomPlaylist(playlists)
libraryViewModel.forceReload(ReloadType.Playlists)
}
.create()
.colorButtons()
}
companion object {
fun create(playlist: Playlist): DeletePlaylistDialog {
val list = ArrayList<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
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import io.github.muntashirakon.music.EXTRA_SONG
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.saf.SAFGuideActivity
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.SAFUtil
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeleteSongsDialog : DialogFragment() {
@JvmField
var currentSong: Song? = null
@JvmField
var songsToRemove: List<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()
})
}
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object {
fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>()
list.add(song)
@ -119,5 +33,38 @@ class DeleteSongsDialog : DialogFragment() {
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
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.provider.MediaStore.Audio.Playlists.Members.PLAYLIST_ID
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil
import io.github.muntashirakon.music.EXTRA_PLAYLIST_ID
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.extensions.accentColor
import io.github.muntashirakon.music.extensions.colorButtons
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.extensions.materialDialog
import io.github.muntashirakon.music.util.PlaylistsUtil
import io.github.muntashirakon.music.fragments.LibraryViewModel
import io.github.muntashirakon.music.fragments.ReloadType
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RenamePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams")
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val layout = LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_playlist, null)
companion object {
fun create(playlistEntity: PlaylistEntity): RenamePlaylistDialog {
return RenamePlaylistDialog().apply {
arguments = bundleOf(
EXTRA_PLAYLIST_ID to playlistEntity
)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistEntity = extraNotNull<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 nameContainer: TextInputLayout =
layout.findViewById(R.id.actionNewPlaylistContainer)
MaterialUtil.setTint(nameContainer, false)
val nameContainer: TextInputLayout = layout.findViewById(R.id.actionNewPlaylistContainer)
nameContainer.accentColor()
inputEditText.setText(playlistEntity.playlistName)
return materialDialog(R.string.rename_playlist_title)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_rename) { _, _ ->
val name = inputEditText.text.toString()
if (name.isNotEmpty()) {
PlaylistsUtil.renamePlaylist(
requireContext(),
extraNotNull<Long>(PLAYLIST_ID).value,
name
)
libraryViewModel.renameRoomPlaylist(playlistEntity.playListId, name)
libraryViewModel.forceReload(ReloadType.Playlists)
} else {
nameContainer.error = "Playlist name should'nt be empty"
}
}
.create()
.colorButtons()
}
companion object {
fun create(playlistId: Long): RenamePlaylistDialog {
val dialog = RenamePlaylistDialog()
val args = Bundle()
args.putLong(PLAYLIST_ID, playlistId)
dialog.arguments = args
return dialog
}
}
}

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

View file

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

View file

@ -15,13 +15,8 @@
package io.github.muntashirakon.music.extensions
import android.app.Activity
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R
import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
@ -30,41 +25,6 @@ fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
setSupportActionBar(toolbar)
}
fun FragmentActivity?.addFragment(
@IdRes idRes: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this as? AppCompatActivity ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
add(fragment, tag)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
if (addToBackStack) {
addToBackStack(null)
}
commitNow()
}
}
fun AppCompatActivity.replaceFragment(
@IdRes id: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
replace(id, fragment, tag)
if (addToBackStack) {
addToBackStack(null)
}
commit()
}
}
inline fun <reified T : Any> Activity.extra(key: String, default: T? = null) = lazy {
val value = intent?.extras?.get(key)
if (value is T) value else default

View file

@ -18,12 +18,18 @@ import android.app.Dialog
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.widget.Button
import android.widget.CheckBox
import android.widget.SeekBar
import androidx.annotation.AttrRes
import androidx.annotation.CheckResult
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
@ -31,6 +37,8 @@ import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.ProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import io.github.muntashirakon.music.App
@ -41,6 +49,7 @@ fun Int.ripAlpha(): Int {
}
fun Dialog.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal)
fun Toolbar.backgroundTintList() {
val surfaceColor = ATHUtil.resolveColor(context, R.attr.colorSurface, Color.BLACK)
val colorStateList = ColorStateList.valueOf(surfaceColor)
@ -76,7 +85,6 @@ fun Fragment.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
fun Dialog.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(context, attr, fallBackColor)
fun CheckBox.addAccentColor() {
buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context))
}
@ -91,6 +99,23 @@ fun Button.accentTextColor() {
setTextColor(ThemeStore.accentColor(App.getContext()))
}
fun MaterialButton.accentTextColor() {
setTextColor(ThemeStore.accentColor(App.getContext()))
}
fun MaterialButton.accentBackgroundColor() {
backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(App.getContext()))
}
fun MaterialButton.accentOutlineColor() {
val color = ThemeStore.accentColor(context)
val colorStateList = ColorStateList.valueOf(color)
iconTint = colorStateList
strokeColor = colorStateList
setTextColor(colorStateList)
rippleColor = colorStateList
}
fun SeekBar.applyColor(@ColorInt color: Int) {
thumbTintList = ColorStateList.valueOf(color)
progressTintList = ColorStateList.valueOf(color)
@ -107,6 +132,15 @@ fun ExtendedFloatingActionButton.accentColor() {
iconTint = textColorStateList
}
fun FloatingActionButton.accentColor() {
val color = ThemeStore.accentColor(context)
val textColor = MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))
val colorStateList = ColorStateList.valueOf(color)
val textColorStateList = ColorStateList.valueOf(textColor)
backgroundTintList = colorStateList
imageTintList = textColorStateList
}
fun MaterialButton.applyColor(color: Int) {
val backgroundColorStateList = ColorStateList.valueOf(color)
val textColorColorStateList = ColorStateList.valueOf(
@ -120,6 +154,12 @@ fun MaterialButton.applyColor(color: Int) {
iconTint = textColorColorStateList
}
fun MaterialButton.applyOutlineColor(color: Int) {
val textColorColorStateList = ColorStateList.valueOf(color)
setTextColor(textColorColorStateList)
iconTint = textColorColorStateList
}
fun TextInputLayout.accentColor() {
val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor)
@ -128,6 +168,39 @@ fun TextInputLayout.accentColor() {
isHintAnimationEnabled = true
}
fun ProgressIndicator.accentColor() {
val accentColor = ThemeStore.accentColor(context)
indicatorColors = intArrayOf(accentColor)
trackColor = ColorUtil.withAlpha(accentColor, 0.2f)
}
fun ProgressIndicator.applyColor(color: Int) {
indicatorColors = intArrayOf(color)
trackColor = ColorUtil.withAlpha(color, 0.2f)
}
fun TextInputEditText.accentColor() {
}
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 {
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
R.style.MaterialAlertDialogTheme
).setTitle(title)
}

View file

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

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.view.View
import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -10,15 +15,14 @@ import io.github.muntashirakon.music.*
import io.github.muntashirakon.music.adapter.album.AlbumAdapter
import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter
import io.github.muntashirakon.music.db.toSong
import io.github.muntashirakon.music.fragments.albums.AlbumClickListener
import io.github.muntashirakon.music.fragments.artists.ArtistClickListener
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
@ -34,6 +38,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
super.onActivityCreated(savedInstanceState)
mainActivity.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
when (args.type) {
TOP_ARTISTS -> {
loadArtists(R.string.top_artists, TOP_ARTISTS)
@ -47,32 +52,88 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
RECENT_ALBUMS -> {
loadAlbums(R.string.recent_albums, RECENT_ALBUMS)
}
FAVOURITES -> {
loadFavorite()
FAVOURITES -> loadFavorite()
HISTORY_PLAYLIST -> loadHistory()
LAST_ADDED_PLAYLIST -> lastAddedSongs()
TOP_PLAYED_PLAYLIST -> topPlayed()
}
}
private fun lastAddedSongs() {
toolbar.setTitle(R.string.last_added)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.recentSongs()
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun topPlayed() {
toolbar.setTitle(R.string.my_top_tracks)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.playCountSongs().map {
it.toSong()
}
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun loadHistory() {
toolbar.setTitle(R.string.history)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
repository.observableHistorySongs().observe(viewLifecycleOwner, Observer {
val songs = it.map { historyEntity -> historyEntity.toSong() }
songAdapter.swapDataSet(songs)
})
}
private fun loadFavorite() {
toolbar.setTitle(R.string.favorites)
CoroutineScope(IO).launch {
val songs = repository.favoritePlaylistHome()
withContext(Main) {
recyclerView.apply {
adapter = SongAdapter(
val songAdapter = SongAdapter(
requireActivity(),
songs.arrayList as MutableList<Song>,
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
}
}
repository.favorites().observe(viewLifecycleOwner, Observer {
println(it.size)
val songs = it.map { songEntity -> songEntity.toSong() }
songAdapter.swapDataSet(songs)
})
}
private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title)
CoroutineScope(IO).launch {
lifecycleScope.launch(IO) {
val artists =
if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists()
withContext(Main) {
@ -86,7 +147,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title)
CoroutineScope(IO).launch {
lifecycleScope.launch(IO) {
val albums =
if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums()
withContext(Main) {
@ -120,11 +181,21 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
GridLayoutManager(requireContext(), 2, GridLayoutManager.VERTICAL, false)
override fun onArtist(artistId: Int, imageView: ImageView) {
override fun onArtist(artistId: Long, imageView: ImageView) {
findNavController().navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId),
null,
FragmentNavigatorExtras(imageView to getString(R.string.transition_artist_image))
)
}
override fun onAlbumClick(albumId: Int, view: View) {
override fun onAlbumClick(albumId: Long, view: View) {
findNavController().navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(view to getString(R.string.transition_album_art))
)
}
}

View file

@ -4,83 +4,130 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.PlaylistWithSongs
import io.github.muntashirakon.music.db.SongEntity
import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.fragments.ReloadType.*
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
import io.github.muntashirakon.music.model.*
import io.github.muntashirakon.music.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class LibraryViewModel(
private val realRepository: RealRepository
private val repository: RealRepository
) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>()
private val albums = MutableLiveData<List<Album>>()
private val songs = MutableLiveData<List<Song>>()
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 home = MutableLiveData<List<Home>>()
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 {
viewModelScope.launch {
loadLibraryContent()
fetchHomeSections()
}
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 {
songs.value = loadSongs.await()
albums.value = loadAlbums.await()
artists.value = loadArtists.await()
playlists.value = loadPlaylists.await()
genres.value = loadGenres.await()
home.value = loadHome.await()
private fun fetchAlbums() {
viewModelScope.launch(IO) {
albums.postValue(repository.fetchAlbums())
}
}
private val loadHome: Deferred<List<Home>>
get() = viewModelScope.async { realRepository.homeSections() }
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 fun fetchArtists() {
viewModelScope.launch(IO) {
artists.postValue(repository.fetchArtists())
}
}
private val loadArtists: Deferred<List<Artist>>
get() = viewModelScope.async(IO) {
realRepository.albumArtists()
private fun fetchPlaylists() {
viewModelScope.launch(IO) {
playlists.postValue(repository.fetchPlaylistWithSongs())
}
}
private val loadPlaylists: Deferred<List<Playlist>>
get() = viewModelScope.async(IO) {
realRepository.allPlaylists()
private fun fetchLegacyPlaylist() {
viewModelScope.launch(IO) {
legacyPlaylists.postValue(repository.fetchLegacyPlaylist())
}
}
private val loadGenres: Deferred<List<Genre>>
get() = viewModelScope.async(IO) {
realRepository.allGenres()
private fun fetchGenres() {
viewModelScope.launch(IO) {
genres.postValue(repository.fetchGenres())
}
}
private fun fetchHomeSections() {
viewModelScope.launch(IO) {
home.postValue(repository.homeSections())
}
}
fun forceReload(reloadType: ReloadType) = viewModelScope.launch {
when (reloadType) {
Songs -> songs.value = loadSongs.await()
Albums -> albums.value = loadAlbums.await()
Artists -> artists.value = loadArtists.await()
HomeSections -> songs.value = loadSongs.await()
Songs -> fetchSongs()
Albums -> fetchAlbums()
Artists -> fetchArtists()
HomeSections -> fetchHomeSections()
Playlists -> fetchPlaylists()
Genres -> fetchGenres()
}
}
@ -89,11 +136,10 @@ class LibraryViewModel(
}
override fun onMediaStoreChanged() {
loadLibraryContent()
println("onMediaStoreChanged")
loadLibraryContent()
}
override fun onServiceConnected() {
println("onServiceConnected")
}
@ -108,6 +154,7 @@ class LibraryViewModel(
override fun onPlayingMetaChanged() {
println("onPlayingMetaChanged")
}
override fun onPlayStateChanged() {
@ -122,11 +169,76 @@ class LibraryViewModel(
println("onShuffleModeChanged")
}
fun shuffleSongs() = viewModelScope.launch(IO) {
val songs = repository.allSongs()
MusicPlayerRemote.openAndShuffleQueue(
songs,
true
)
}
fun renameRoomPlaylist(playListId: Long, name: String) = viewModelScope.launch(IO) {
repository.renameRoomPlaylist(playListId, name)
}
fun deleteSongsInPlaylist(songs: List<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 {
Songs,
Albums,
Artists,
HomeSections
HomeSections,
Playlists,
Genres,
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,26 +1,35 @@
package io.github.muntashirakon.music.fragments.albums
import android.os.Bundle
import android.view.View
import android.view.*
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager
import io.github.muntashirakon.music.EXTRA_ALBUM_ID
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.album.AlbumAdapter
import io.github.muntashirakon.music.extensions.findActivityNavController
import io.github.muntashirakon.music.fragments.ReloadType
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import io.github.muntashirakon.music.helper.SortOrder
import io.github.muntashirakon.music.helper.SortOrder.AlbumSortOrder
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.transition.platform.MaterialFadeThrough
class AlbumsFragment :
AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
class AlbumsFragment : AbsRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager>(),
AlbumClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.albumsLiveData.observe(viewLifecycleOwner, Observer {
libraryViewModel.getAlbums().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty())
adapter?.swapDataSet(it)
else
@ -40,7 +49,7 @@ class AlbumsFragment :
return AlbumAdapter(
requireActivity(),
dataSet,
R.layout.item_grid,
itemLayoutRes(),
null,
this
)
@ -94,9 +103,8 @@ class AlbumsFragment :
}
}
override fun onAlbumClick(albumId: Int, view: View) {
val controller = requireActivity().findNavController(R.id.fragment_container)
controller.navigate(
override fun onAlbumClick(albumId: Long, view: View) {
findActivityNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
@ -105,8 +113,183 @@ class AlbumsFragment :
)
)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size)
if (RetroUtil.isLandscape()) {
gridSizeItem.setTitle(R.string.action_grid_size_land)
}
setUpGridSizeMenu(gridSizeItem.subMenu)
val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun setUpSortOrderMenu(
sortOrderMenu: SubMenu
) {
val currentSortOrder: String? = getSortOrder()
sortOrderMenu.clear()
sortOrderMenu.add(
0,
R.id.action_album_sort_order_asc,
0,
R.string.sort_order_a_z
).isChecked =
currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z)
sortOrderMenu.add(
0,
R.id.action_album_sort_order_desc,
1,
R.string.sort_order_z_a
).isChecked =
currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)
sortOrderMenu.add(
0,
R.id.action_album_sort_order_artist,
2,
R.string.sort_order_artist
).isChecked =
currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)
sortOrderMenu.add(
0,
R.id.action_album_sort_order_year,
3,
R.string.sort_order_year
).isChecked =
currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_YEAR)
sortOrderMenu.setGroupCheckable(0, true, true)
}
private fun setupLayoutMenu(
subMenu: SubMenu
) {
when (itemLayoutRes()) {
R.layout.item_card -> subMenu.findItem(R.id.action_layout_card).isChecked = true
R.layout.item_grid -> subMenu.findItem(R.id.action_layout_normal).isChecked = true
R.layout.item_card_color ->
subMenu.findItem(R.id.action_layout_colored_card).isChecked = true
R.layout.item_grid_circle ->
subMenu.findItem(R.id.action_layout_circular).isChecked = true
R.layout.image -> subMenu.findItem(R.id.action_layout_image).isChecked = true
R.layout.item_image_gradient ->
subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true
}
}
private fun setUpGridSizeMenu(
gridSizeMenu: SubMenu
) {
when (getGridSize()) {
1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked =
true
2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true
3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true
4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true
5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true
6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true
7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true
8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true
}
val gridSize: Int = maxGridSize
if (gridSize < 8) {
gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false
}
if (gridSize < 7) {
gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false
}
if (gridSize < 6) {
gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false
}
if (gridSize < 5) {
gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false
}
if (gridSize < 4) {
gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false
}
if (gridSize < 3) {
gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (handleGridSizeMenuItem(item)) {
return true
}
if (handleLayoutResType(item)) {
return true
}
if (handleSortOrderMenuItem(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
private fun handleSortOrderMenuItem(
item: MenuItem
): Boolean {
var sortOrder: String? = null
when (item.itemId) {
R.id.action_album_sort_order_asc -> sortOrder = AlbumSortOrder.ALBUM_A_Z
R.id.action_album_sort_order_desc -> sortOrder = AlbumSortOrder.ALBUM_Z_A
R.id.action_album_sort_order_artist -> sortOrder = AlbumSortOrder.ALBUM_ARTIST
R.id.action_album_sort_order_year -> sortOrder = AlbumSortOrder.ALBUM_YEAR
}
if (sortOrder != null) {
item.isChecked = true
setAndSaveSortOrder(sortOrder)
return true
}
return false
}
private fun handleLayoutResType(
item: MenuItem
): Boolean {
var layoutRes = -1
when (item.itemId) {
R.id.action_layout_normal -> layoutRes = R.layout.item_grid
R.id.action_layout_card -> layoutRes = R.layout.item_card
R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color
R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle
R.id.action_layout_image -> layoutRes = R.layout.image
R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient
}
if (layoutRes != -1) {
item.isChecked = true
setAndSaveLayoutRes(layoutRes)
return true
}
return false
}
private fun handleGridSizeMenuItem(
item: MenuItem
): Boolean {
var gridSize = 0
when (item.itemId) {
R.id.action_grid_size_1 -> gridSize = 1
R.id.action_grid_size_2 -> gridSize = 2
R.id.action_grid_size_3 -> gridSize = 3
R.id.action_grid_size_4 -> gridSize = 4
R.id.action_grid_size_5 -> gridSize = 5
R.id.action_grid_size_6 -> gridSize = 6
R.id.action_grid_size_7 -> gridSize = 7
R.id.action_grid_size_8 -> gridSize = 8
}
if (gridSize > 0) {
item.isChecked = true
setAndSaveGridSize(gridSize)
return true
}
return false
}
}
interface AlbumClickListener {
fun onAlbumClick(albumId: Int, view: View)
fun onAlbumClick(albumId: Long, view: View)
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
package io.github.muntashirakon.music.fragments.artists
import android.os.Bundle
import android.view.View
import android.view.*
import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
@ -12,21 +12,22 @@ import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
import io.github.muntashirakon.music.extensions.findActivityNavController
import io.github.muntashirakon.music.fragments.ReloadType
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewCustomGridSizeFragment
import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks
import io.github.muntashirakon.music.helper.SortOrder.ArtistSortOrder
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.transition.platform.MaterialFadeThrough
class ArtistsFragment :
AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
MainActivityFragmentCallbacks, ArtistClickListener {
override fun handleBackPress(): Boolean {
return false
class ArtistsFragment : AbsRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager>(),
ArtistClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.artistsLiveData.observe(viewLifecycleOwner, Observer {
libraryViewModel.getArtists().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty())
adapter?.swapDataSet(it)
else
@ -50,7 +51,7 @@ class ArtistsFragment :
return ArtistAdapter(
requireActivity(),
dataSet,
R.layout.item_grid_circle,
itemLayoutRes(),
null,
this
)
@ -100,12 +101,167 @@ class ArtistsFragment :
}
}
override fun onArtist(artistId: Int, imageView: ImageView) {
override fun onArtist(artistId: Long, imageView: ImageView) {
val controller = findActivityNavController(R.id.fragment_container)
controller.navigate(R.id.artistDetailsFragment, bundleOf(EXTRA_ARTIST_ID to artistId))
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size)
if (RetroUtil.isLandscape()) {
gridSizeItem.setTitle(R.string.action_grid_size_land)
}
setUpGridSizeMenu(gridSizeItem.subMenu)
val layoutItem = menu.findItem(R.id.action_layout_type)
setupLayoutMenu(layoutItem.subMenu)
setUpSortOrderMenu(menu.findItem(R.id.action_sort_order).subMenu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun setUpSortOrderMenu(
sortOrderMenu: SubMenu
) {
val currentSortOrder: String? = getSortOrder()
sortOrderMenu.clear()
sortOrderMenu.add(
0,
R.id.action_artist_sort_order_asc,
0,
R.string.sort_order_a_z
).isChecked = currentSortOrder.equals(ArtistSortOrder.ARTIST_A_Z)
sortOrderMenu.add(
0,
R.id.action_artist_sort_order_desc,
1,
R.string.sort_order_z_a
).isChecked = currentSortOrder.equals(ArtistSortOrder.ARTIST_Z_A)
sortOrderMenu.setGroupCheckable(0, true, true)
}
private fun setupLayoutMenu(
subMenu: SubMenu
) {
when (itemLayoutRes()) {
R.layout.item_card -> subMenu.findItem(R.id.action_layout_card).isChecked = true
R.layout.item_grid -> subMenu.findItem(R.id.action_layout_normal).isChecked = true
R.layout.item_card_color ->
subMenu.findItem(R.id.action_layout_colored_card).isChecked = true
R.layout.item_grid_circle ->
subMenu.findItem(R.id.action_layout_circular).isChecked = true
R.layout.image -> subMenu.findItem(R.id.action_layout_image).isChecked = true
R.layout.item_image_gradient ->
subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true
}
}
private fun setUpGridSizeMenu(
gridSizeMenu: SubMenu
) {
when (getGridSize()) {
1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked =
true
2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true
3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true
4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true
5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true
6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true
7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true
8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true
}
val gridSize: Int = maxGridSize
if (gridSize < 8) {
gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false
}
if (gridSize < 7) {
gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false
}
if (gridSize < 6) {
gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false
}
if (gridSize < 5) {
gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false
}
if (gridSize < 4) {
gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false
}
if (gridSize < 3) {
gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (handleGridSizeMenuItem(item)) {
return true
}
if (handleLayoutResType(item)) {
return true
}
if (handleSortOrderMenuItem(item)) {
return true
}
return super.onOptionsItemSelected(item)
}
private fun handleSortOrderMenuItem(
item: MenuItem
): Boolean {
var sortOrder: String? = null
when (item.itemId) {
R.id.action_artist_sort_order_asc -> sortOrder = ArtistSortOrder.ARTIST_A_Z
R.id.action_artist_sort_order_desc -> sortOrder = ArtistSortOrder.ARTIST_Z_A
}
if (sortOrder != null) {
item.isChecked = true
setAndSaveSortOrder(sortOrder)
return true
}
return false
}
private fun handleLayoutResType(
item: MenuItem
): Boolean {
var layoutRes = -1
when (item.itemId) {
R.id.action_layout_normal -> layoutRes = R.layout.item_grid
R.id.action_layout_card -> layoutRes = R.layout.item_card
R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color
R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle
R.id.action_layout_image -> layoutRes = R.layout.image
R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient
}
if (layoutRes != -1) {
item.isChecked = true
setAndSaveLayoutRes(layoutRes)
return true
}
return false
}
private fun handleGridSizeMenuItem(
item: MenuItem
): Boolean {
var gridSize = 0
when (item.itemId) {
R.id.action_grid_size_1 -> gridSize = 1
R.id.action_grid_size_2 -> gridSize = 2
R.id.action_grid_size_3 -> gridSize = 3
R.id.action_grid_size_4 -> gridSize = 4
R.id.action_grid_size_5 -> gridSize = 5
R.id.action_grid_size_6 -> gridSize = 6
R.id.action_grid_size_7 -> gridSize = 7
R.id.action_grid_size_8 -> gridSize = 8
}
if (gridSize > 0) {
item.isChecked = true
setAndSaveGridSize(gridSize)
return true
}
return false
}
}
interface ArtistClickListener {
fun onArtist(artistId: Int, imageView: ImageView)
fun onArtist(artistId: Long, imageView: ImageView)
}

View file

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

View file

@ -79,7 +79,6 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
}
private fun checkIsEmpty() {
emptyEmoji.text = getEmojiByUnicode(0x1F631)
emptyText.setText(emptyMessage)
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.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
setupRecyclerView()
detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer {
songs(it)

View file

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

View file

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

View file

@ -4,21 +4,26 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.text.HtmlCompat
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.NavigationUI
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import com.google.android.material.appbar.AppBarLayout
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.dialogs.CreatePlaylistDialog
import io.github.muntashirakon.music.dialogs.ImportPlaylistDialog
import io.github.muntashirakon.music.extensions.findNavController
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import kotlinx.android.synthetic.main.fragment_library.*
import java.lang.String
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
retainInstance = true
mainActivity.hideBottomBarVisibility(true)
mainActivity.setSupportActionBar(toolbar)
mainActivity.supportActionBar?.title = null
@ -30,6 +35,17 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
)
}
setupNavigationController()
setupTitle()
}
private fun setupTitle() {
val color = ThemeStore.accentColor(requireContext())
val hexColor = String.format("#%06X", 0xFFFFFF and color)
val appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
}
private fun setupNavigationController() {
@ -60,19 +76,15 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
null,
navOptions
)
R.id.action_import_playlist -> ImportPlaylistDialog().show(
childFragmentManager,
"ImportPlaylist"
)
R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show(
childFragmentManager,
"ShowCreatePlaylistDialog"
)
}
return super.onOptionsItemSelected(item)
}
fun addOnAppBarOffsetChangedListener(changedListener: AppBarLayout.OnOffsetChangedListener) {
appBarLayout.addOnOffsetChangedListener(changedListener)
}
fun removeOnAppBarOffsetChangedListener(changedListener: AppBarLayout.OnOffsetChangedListener) {
appBarLayout.removeOnOffsetChangedListener(changedListener)
}
fun getTotalAppBarScrollingRange(): Int {
return 0
}
}

View file

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

View file

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

View file

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.AsyncTask
import android.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
@ -15,14 +14,16 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.RetroBottomSheetBehavior
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
import io.github.muntashirakon.music.adapter.song.PlayingQueueAdapter
import io.github.muntashirakon.music.db.PlaylistEntity
import io.github.muntashirakon.music.db.SongEntity
import io.github.muntashirakon.music.db.toSongEntity
import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.extensions.ripAlpha
import io.github.muntashirakon.music.extensions.show
@ -39,7 +40,7 @@ import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.ViewUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.*
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
@ -48,6 +49,9 @@ import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.fragment_gradient_controls.*
import kotlinx.android.synthetic.main.fragment_gradient_player.*
import kotlinx.android.synthetic.main.status_bar.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_player),
MusicProgressViewUpdateHelper.Callback,
@ -62,14 +66,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
private val bottomSheetCallbackList = object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
playerQueueSheet.setPadding(
playerQueueSheet.paddingLeft,
(slideOffset * status_bar.height).toInt(),
@ -79,18 +80,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) {
BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false)
STATE_EXPANDED,
STATE_DRAGGING -> {
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
}
BottomSheetBehavior.STATE_COLLAPSED -> {
STATE_COLLAPSED -> {
resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true)
mainActivity.getBottomSheetBehavior().setAllowDragging(true)
}
else -> {
activity.getBottomSheetBehavior().setAllowDragging(true)
mainActivity.getBottomSheetBehavior().setAllowDragging(true)
}
}
}
@ -139,8 +139,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun setupSheet() {
getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList)
playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false
}
@ -159,7 +158,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
recyclerViewDragDropManager?.cancelDrag()
super.onPause()
progressViewUpdateHelper.stop()
}
override fun playerToolbar(): Toolbar? {
@ -176,9 +174,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onBackPressed(): Boolean {
var wasExpanded = false
if (getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED) {
wasExpanded = getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED
getQueuePanel().state = BottomSheetBehavior.STATE_COLLAPSED
if (getQueuePanel().state == STATE_EXPANDED) {
wasExpanded = getQueuePanel().state == STATE_EXPANDED
getQueuePanel().state = STATE_COLLAPSED
return wasExpanded
}
return wasExpanded
@ -224,9 +222,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun toggleFavorite(song: Song) {
super.toggleFavorite(song)
MusicUtil.toggleFavorite(requireContext(), song)
if (song.id == MusicPlayerRemote.currentSong.id) {
updateFavorite()
updateIsFavoriteIcon()
}
}
@ -234,6 +231,23 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
toggleFavorite(MusicPlayerRemote.currentSong)
}
private fun updateIsFavoriteIcon() {
lifecycleScope.launch(Dispatchers.IO) {
val playlist: PlaylistEntity? = libraryViewModel.favoritePlaylist()
if (playlist != null) {
val song: SongEntity =
MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite: Boolean = libraryViewModel.isFavoriteSong(song).isNotEmpty()
withContext(Dispatchers.Main) {
val icon =
if (isFavorite) R.drawable.ic_favorite
else R.drawable.ic_favorite_border
songFavourite.setImageResource(icon)
}
}
}
}
private fun hideVolumeIfAvailable() {
if (PreferenceUtil.isVolumeVisibilityMode) {
childFragmentManager.beginTransaction()
@ -251,6 +265,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
updatePlayPauseDrawableState()
updatePlayPauseDrawableState()
updateQueue()
updateIsFavoriteIcon()
}
override fun onPlayStateChanged() {
@ -269,11 +284,13 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
super.onPlayingMetaChanged()
updateSong()
updateQueuePosition()
updateIsFavoriteIcon()
}
override fun onQueueChanged() {
super.onQueueChanged()
updateLabel()
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue)
}
private fun updateSong() {
@ -368,13 +385,10 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun updateLabel() {
(MusicPlayerRemote.playingQueue.size - 1).apply {
if (this == (MusicPlayerRemote.position)) {
nextSong.hide()
nextSong.text = "Last song"
} else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
nextSong.apply {
text = "Next: $title"
show()
}
nextSong.text = title
}
}
}
@ -469,45 +483,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
@SuppressLint("StaticFieldLeak")
private fun updateFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask?.cancel(false)
}
updateIsFavoriteTask =
object : AsyncTask<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.MenuItem
import android.view.View
import androidx.lifecycle.Observer
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter
import io.github.muntashirakon.music.db.PlaylistWithSongs
import io.github.muntashirakon.music.db.toSongs
import io.github.muntashirakon.music.extensions.dipToPix
import io.github.muntashirakon.music.fragments.base.AbsMainActivityFragment
import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper
import io.github.muntashirakon.music.model.AbsCustomPlaylist
import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.PlaylistsUtil
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
@ -32,7 +31,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
parametersOf(arguments.extraPlaylist)
}
private lateinit var playlist: Playlist
private lateinit var playlist: PlaylistWithSongs
private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
@ -46,28 +45,22 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
mainActivity.hideBottomBarVisibility(false)
playlist = arguments.extraPlaylist
toolbar.title = playlist.playlistEntity.playlistName
setUpRecyclerView()
viewModel.getSongs().observe(viewLifecycleOwner, Observer {
songs(it)
})
viewModel.getPlaylist().observe(viewLifecycleOwner, Observer {
playlist = it
toolbar.title = it.name
viewModel.getSongs().observe(viewLifecycleOwner, {
songs(it.toSongs())
})
}
private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(requireContext())
if (playlist is AbsCustomPlaylist) {
adapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null)
recyclerView.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter(
adapter =
OrderablePlaylistSongAdapter(
playlist.playlistEntity,
requireActivity(),
ArrayList(),
R.layout.item_list,
@ -76,7 +69,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem(
requireContext(),
playlist.id,
playlist.playlistEntity.playListId,
fromPosition,
toPosition
)
@ -93,7 +86,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
recyclerView.itemAnimator = animator
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
@ -104,9 +97,9 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val menuRes = if (playlist is AbsCustomPlaylist)
val menuRes =/* if (playlist is AbsCustomPlaylist)
R.menu.menu_smart_playlist_detail
else R.menu.menu_playlist_detail
else*/ R.menu.menu_playlist_detail
inflater.inflate(menuRes, menu)
}
@ -121,15 +114,10 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
@ -161,11 +149,11 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
fun songs(songs: List<Song>) {
progressIndicator.hide()
if (songs.isNotEmpty()) {
adapter.swapDataSet(songs)
} else {
showEmptyView()
}
}
}

View file

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

View file

@ -1,25 +1,28 @@
package io.github.muntashirakon.music.fragments.playlists
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.playlist.PlaylistAdapter
import io.github.muntashirakon.music.fragments.base.AbsRecyclerViewFragment
import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks
import com.google.android.material.transition.platform.MaterialFadeThrough
import kotlinx.android.synthetic.main.fragment_library.*
class PlaylistsFragment :
AbsRecyclerViewFragment<PlaylistAdapter, GridLayoutManager>(),
MainActivityFragmentCallbacks {
override fun handleBackPress(): Boolean {
return false
class PlaylistsFragment : AbsRecyclerViewFragment<PlaylistAdapter, LinearLayoutManager>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.playlisitsLiveData.observe(viewLifecycleOwner, Observer {
libraryViewModel.getPlaylists().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty())
adapter?.swapDataSet(it)
else
@ -30,8 +33,8 @@ class PlaylistsFragment :
override val emptyMessage: Int
get() = R.string.no_playlists
override fun createLayoutManager(): GridLayoutManager {
return GridLayoutManager(requireContext(), 1)
override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(requireContext())
}
override fun createAdapter(): PlaylistAdapter {
@ -43,9 +46,18 @@ class PlaylistsFragment :
)
}
companion object {
fun newInstance(): PlaylistsFragment {
return PlaylistsFragment()
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.removeItem(R.id.action_grid_size)
menu.removeItem(R.id.action_layout_type)
menu.removeItem(R.id.action_sort_order)
menu.add(0, R.id.action_add_to_playlist, 0, R.string.new_playlist_title)
menu.add(0, R.id.action_import_playlist, 0, R.string.import_playlist)
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
super.onCreateOptionsMenu(menu, inflater)
}
}

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

View file

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

View file

@ -14,13 +14,19 @@
package io.github.muntashirakon.music.fragments.settings
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.extensions.show
import io.github.muntashirakon.music.util.NavigationUtil
import kotlinx.android.synthetic.main.fragment_main_settings.*
class MainSettingsFragment : Fragment(), View.OnClickListener {

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