Merge pull request #883 from h4h13/room-playlist

Room playlist
main
Hemanth S 2020-09-12 12:27:37 +05:30 committed by GitHub
commit fbbc1a1ef9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
212 changed files with 3963 additions and 1957 deletions

6
.gitignore vendored
View File

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

View File

@ -68,7 +68,7 @@ favorite songs. No other music player has this feature.
We are trying our best to bring you the best user experience. The app is regulary being updated for bug fixes and new features. We are trying our best to bring you the best user experience. The app is regulary being updated for bug fixes and new features.
### FAQ ### FAQ
Please read the FAQ here: https://del.dog/RetroFaq Please read the FAQ here: https://del.dog/RetroFaq
In any case, you find or notice any bugs please report them by In any case, you find or notice any bugs please report them by

View File

@ -34,7 +34,7 @@ android {
} }
signingConfigs { signingConfigs {
release { release {
Properties properties = getProperties('/Users/h4h13/Documents/Github/retro.properties') Properties properties = getProperties('/Users/apple/Documents/Github/retro.properties ')
storeFile file(getProperty(properties, 'storeFile')) storeFile file(getProperty(properties, 'storeFile'))
keyAlias getProperty(properties, 'keyAlias') keyAlias getProperty(properties, 'keyAlias')
storePassword getProperty(properties, 'storePassword') storePassword getProperty(properties, 'storePassword')
@ -102,68 +102,50 @@ static def getDate() {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':appthemehelper') implementation project(':appthemehelper')
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8' def nav_version = "2.3.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'com.google.android.material:material:1.3.0-alpha01' def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
def retrofit_version = '2.9.0' implementation "androidx.room:room-ktx:$room_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" kapt "androidx.room:room-compiler:$room_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
def material_dialog_version = "0.9.6.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') {
transitive = true
}
def kotlin_coroutines_version = "1.3.8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
def lifecycle_version = "2.2.0" def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.google.android.play:core:1.8.0' implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'com.google.android.material:material:1.3.0-alpha01'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
def material_dialog_version = "0.9.6.0"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
def kotlin_coroutines_version = "1.3.8"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
def koin_version = "2.1.5" def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version" implementation "org.koin:koin-core:$koin_version"
@ -173,8 +155,22 @@ dependencies {
implementation "org.koin:koin-androidx-fragment:$koin_version" implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version" implementation "org.koin:koin-androidx-ext:$koin_version"
def nav_version = "2.3.0" implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
implementation 'org.jsoup:jsoup:1.11.1'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
} }

View File

@ -1,20 +0,0 @@
{
"version": 1,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "code.name.monkey.retromusic",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"properties": [],
"versionCode": 10438,
"versionName": "3.5.650_0812",
"enabled": true,
"outputFile": "app-release.apk"
}
]
}

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

@ -255,11 +255,9 @@
<meta-data <meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule" android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" /> android:value="GlideModule" />
<meta-data <meta-data
android:name="com.android.vending.splits.required" android:name="com.android.vending.splits.required"
android:value="true" /> android:value="true" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

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

View File

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

View File

@ -1,5 +1,12 @@
package code.name.monkey.retromusic package code.name.monkey.retromusic
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import code.name.monkey.retromusic.db.BlackListStoreDao
import code.name.monkey.retromusic.db.BlackListStoreEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.RetroDatabase
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
@ -7,17 +14,103 @@ import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
import code.name.monkey.retromusic.fragments.search.SearchViewModel import code.name.monkey.retromusic.fragments.search.SearchViewModel
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.network.*
import code.name.monkey.retromusic.network.networkModule
import code.name.monkey.retromusic.repository.* import code.name.monkey.retromusic.repository.*
import code.name.monkey.retromusic.util.FilePathUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
val networkModule = module {
factory {
provideDefaultCache()
}
factory {
provideOkHttp(get(), get())
}
single {
provideLastFmRetrofit(get())
}
single {
provideDeezerRest(get())
}
single {
provideLastFmRest(get())
}
single {
provideLyrics(get())
}
}
private val roomModule = module {
single {
Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
.allowMainThreadQueries()
.addCallback(object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
GlobalScope.launch(IO) {
FilePathUtil.blacklistFilePaths().map {
get<BlackListStoreDao>().insertBlacklistPath(BlackListStoreEntity(it))
}
}
}
})
.fallbackToDestructiveMigration()
.build()
}
factory {
get<RetroDatabase>().lyricsDao()
}
factory {
get<RetroDatabase>().playlistDao()
}
factory {
get<RetroDatabase>().blackListStore()
}
factory {
get<RetroDatabase>().playCountDao()
}
factory {
get<RetroDatabase>().historyDao()
}
single {
RealRoomRepository(get(), get(), get(), get(), get())
} bind RoomRepository::class
}
private val mainModule = module {
single {
androidContext().contentResolver
}
}
private val dataModule = module { private val dataModule = module {
single { single {
RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) RealRepository(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
} bind Repository::class } bind Repository::class
single { single {
@ -61,10 +154,6 @@ private val dataModule = module {
get() get()
) )
} }
single {
androidContext().contentResolver
}
} }
private val viewModules = module { private val viewModules = module {
@ -87,7 +176,7 @@ private val viewModules = module {
) )
} }
viewModel { (playlist: Playlist) -> viewModel { (playlist: PlaylistWithSongs) ->
PlaylistDetailsViewModel( PlaylistDetailsViewModel(
get(), get(),
playlist playlist
@ -106,4 +195,4 @@ private val viewModules = module {
} }
} }
val appModules = listOf(dataModule, viewModules, networkModule) val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)

View File

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

View File

@ -3,30 +3,23 @@ package code.name.monkey.retromusic.activities
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicPlayerRemote.openAndShuffleQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playFromUri
import code.name.monkey.retromusic.helper.MusicPlayerRemote.shuffleMode
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistSongsLoader.getPlaylistSongList import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.AppRater.appLaunched import code.name.monkey.retromusic.util.AppRater.appLaunched
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.util.*
class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener { class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeListener {
companion object { companion object {
@ -35,9 +28,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
const val APP_UPDATE_REQUEST_CODE = 9002 const val APP_UPDATE_REQUEST_CODE = 9002
} }
private val repository by inject<Repository>()
private val libraryViewModel by inject<LibraryViewModel>()
private var blockRequestPermissions = false private var blockRequestPermissions = false
override fun createContentView(): View { override fun createContentView(): View {
@ -53,7 +43,6 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
hideStatusBar() hideStatusBar()
appLaunched(this) appLaunched(this)
addMusicServiceEventListener(libraryViewModel)
updateTabs() updateTabs()
} }
@ -99,61 +88,68 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
handlePlaybackIntent(intent)
}
private fun handlePlaybackIntent(intent: Intent?) {
if (intent == null) { if (intent == null) {
return return
} }
val uri = intent.data handlePlaybackIntent(intent)
val mimeType = intent.type }
var handled = false
if (intent.action != null && (intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) private fun handlePlaybackIntent(intent: Intent) {
) { lifecycleScope.launch(IO) {
val songs: List<Song> = val uri: Uri? = intent.data
getSongs(this, intent.extras!!) val mimeType: String? = intent.type
if (shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) { var handled = false
openAndShuffleQueue(songs, true) if (intent.action != null &&
} else { intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
openQueue(songs, 0, true) ) {
} val songs: List<Song> = getSongs(intent.extras!!)
handled = true if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
} MusicPlayerRemote.openAndShuffleQueue(songs, true)
if (uri != null && uri.toString().isNotEmpty()) { } else {
playFromUri(uri) MusicPlayerRemote.openQueue(songs, 0, true)
handled = true }
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
if (id >= 0) {
val position = intent.getIntExtra("position", 0)
val songs: List<Song> =
ArrayList(getPlaylistSongList(this, id))
openQueue(songs, position, true)
handled = true handled = true
} }
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) { if (uri != null && uri.toString().isNotEmpty()) {
val id = parseIdFromIntent(intent, "albumId", "album").toInt() MusicPlayerRemote.playFromUri(uri)
if (id >= 0) { handled = true
lifecycleScope.launch(Dispatchers.Main) { } else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val position = intent.getIntExtra("position", 0) val id: Int = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
openQueue(repository.albumById(id).songs!!, position, true) if (id >= 0) {
val position: Int = intent.getIntExtra("position", 0)
val songs: List<Song> =
PlaylistSongsLoader.getPlaylistSongList(this@MainActivity, id)
MusicPlayerRemote.openQueue(songs, position, true)
handled = true
}
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
if (id >= 0) {
val position: Int = intent.getIntExtra("position", 0)
MusicPlayerRemote.openQueue(
libraryViewModel.albumById(id).songs!!,
position,
true
)
handled = true
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
val id: Int = parseIdFromIntent(intent, "artistId", "artist").toInt()
if (id >= 0) {
val position: Int = intent.getIntExtra("position", 0)
MusicPlayerRemote.openQueue(
libraryViewModel.artistById(id).songs,
position,
true
)
handled = true handled = true
} }
} }
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) { if (handled) {
val id = parseIdFromIntent(intent, "artistId", "artist").toInt() setIntent(Intent())
if (id >= 0) {
lifecycleScope.launch {
val position = intent.getIntExtra("position", 0)
openQueue(repository.artistById(id).songs, position, true)
handled = true
}
} }
} }
if (handled) {
setIntent(Intent())
}
} }
private fun parseIdFromIntent( private fun parseIdFromIntent(
@ -167,7 +163,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
try { try {
id = idString.toLong() id = idString.toLong()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, e.message) println(e.message)
} }
} }
} }

View File

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

View File

@ -108,7 +108,7 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
super.onDestroy() super.onDestroy()
} }
private class RestorePurchaseAsyncTask internal constructor(purchaseActivity: PurchaseActivity) : private class RestorePurchaseAsyncTask(purchaseActivity: PurchaseActivity) :
AsyncTask<Void, Void, Boolean>() { AsyncTask<Void, Void, Boolean>() {
private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference( private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference(

View File

@ -110,7 +110,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
} }
if (requestCode == TEZ_REQUEST_CODE) { if (requestCode == TEZ_REQUEST_CODE) {
// Process based on the data in response. // Process based on the data in response.
Log.d("result", data!!.getStringExtra("Status")) //Log.d("result", data!!.getStringExtra("Status"))
} }
} }
@ -121,7 +121,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
} }
} }
private class SkuDetailsLoadAsyncTask internal constructor(supportDevelopmentActivity: SupportDevelopmentActivity) : private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelopmentActivity) :
AsyncTask<Void, Void, List<SkuDetails>>() { AsyncTask<Void, Void, List<SkuDetails>>() {
private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference( private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference(

View File

@ -160,7 +160,7 @@ class UserInfoActivity : AbsBaseActivity() {
} }
private fun saveImage(bitmap: Bitmap, fileName: String) { private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch() { CoroutineScope(Dispatchers.IO).launch {
val appDir = applicationContext.filesDir val appDir = applicationContext.filesDir
val file = File(appDir, fileName) val file = File(appDir, fileName)
var successful = false var successful = false

View File

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

View File

@ -15,7 +15,6 @@ import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.MiniPlayerFragment import code.name.monkey.retromusic.fragments.MiniPlayerFragment
@ -29,12 +28,12 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.sliding_music_panel_layout.* import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() { abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
companion object { companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
} }
private val libraryViewModel by viewModel<LibraryViewModel>() protected val libraryViewModel by viewModel<LibraryViewModel>()
private lateinit var behavior: RetroBottomSheetBehavior<FrameLayout> private lateinit var behavior: RetroBottomSheetBehavior<FrameLayout>
private var miniPlayerFragment: MiniPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null
private var cps: NowPlayingScreen? = null private var cps: NowPlayingScreen? = null
@ -51,8 +50,6 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset) setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show()
dimBackground.alpha = slideOffset
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
@ -62,7 +59,6 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() {
} }
BottomSheetBehavior.STATE_COLLAPSED -> { BottomSheetBehavior.STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
dimBackground.hide()
} }
else -> { else -> {
@ -77,13 +73,9 @@ abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() {
setContentView(createContentView()) setContentView(createContentView())
chooseFragmentForTheme() chooseFragmentForTheme()
setupSlidingUpPanel() setupSlidingUpPanel()
addMusicServiceEventListener(libraryViewModel)
setupBottomSheet() setupBottomSheet()
val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY)
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
libraryViewModel.paletteColorLiveData.observe(this, Observer { libraryViewModel.paletteColorLiveData.observe(this, Observer {
this.paletteColor = it this.paletteColor = it
onPaletteColorChanged() onPaletteColorChanged()

View File

@ -295,7 +295,7 @@ open class BugReportActivity : AbsThemeActivity() {
.setTitle(R.string.bug_report_failed) .setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown) .setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() } .setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> { tryToFinishActivity() } } .setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
} }
} }

View File

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

View File

@ -14,7 +14,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -182,11 +181,9 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
getIntentExtras() getIntentExtras()
lifecycleScope.launchWhenCreated { songPaths = getSongPaths()
songPaths = getSongPaths() if (songPaths!!.isEmpty()) {
if (songPaths!!.isEmpty()) { finish()
finish()
}
} }
setUpViews() setUpViews()
} }
@ -258,7 +255,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
} }
} }
protected abstract suspend fun getSongPaths(): List<String> protected abstract fun getSongPaths(): List<String>
protected fun searchWebFor(vararg keys: String) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()

View File

@ -44,9 +44,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
window.enterTransition = slide window.enterTransition = slide
} }
override fun loadImageFromFile(selectedFileUri: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFileUri).asBitmap() Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java) .transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() { .into(object : SimpleTarget<BitmapPaletteWrapper>() {
@ -167,7 +167,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
) )
} }
override suspend fun getSongPaths(): List<String> { override fun getSongPaths(): List<String> {
val songs = repository.albumById(id).songs val songs = repository.albumById(id).songs
val paths = ArrayList<String>(songs!!.size) val paths = ArrayList<String>(songs!!.size)
for (song in songs) { for (song in songs) {

View File

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

View File

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

View File

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

View File

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

View File

@ -101,11 +101,10 @@ open class AlbumAdapter(
} }
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity) .checkIgnoreMediaStore()
.generatePalette(activity) .generatePalette(activity)
.build() .build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder) setColors(colors, holder)
} }

View File

@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.LyricsDialog
import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
@ -90,6 +91,7 @@ class AlbumCoverPagerAdapter(
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
albumCover = view.findViewById(R.id.player_image) albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener { albumCover.setOnClickListener {
LyricsDialog().show(childFragmentManager, "LyricsDialog")
showLyricsDialog() showLyricsDialog()
} }
return view return view
@ -97,7 +99,7 @@ class AlbumCoverPagerAdapter(
private fun showLyricsDialog() { private fun showLyricsDialog() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val data = MusicUtil.getLyrics(song) val data: String = MusicUtil.getLyrics(song) ?: "No lyrics found"
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder( MaterialAlertDialogBuilder(
requireContext(), requireContext(),

View File

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

View File

@ -0,0 +1,52 @@
package code.name.monkey.retromusic.adapter.playlist
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.MusicUtil
class LegacyPlaylistAdapter(
private val activity: FragmentActivity,
private var list: List<Playlist>,
private val layoutRes: Int,
private val playlistClickListener: PlaylistClickListener
) :
RecyclerView.Adapter<LegacyPlaylistAdapter.ViewHolder>() {
fun swapData(list: List<Playlist>) {
this.list = list
notifyDataSetChanged()
}
class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist: Playlist = list[position]
holder.title?.text = playlist.name
holder.text?.text = MusicUtil.getPlaylistInfoString(activity, playlist.getSongs())
holder.itemView.setOnClickListener {
playlistClickListener.onPlaylistClick(playlist)
}
}
override fun getItemCount(): Int {
return list.size
}
interface PlaylistClickListener {
fun onPlaylistClick(playlist: Playlist)
}
}

View File

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

View File

@ -5,7 +5,9 @@ import android.view.View
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.menu import code.name.monkey.retromusic.R.menu
import code.name.monkey.retromusic.dialogs.RemoveFromPlaylistDialog import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
@ -16,6 +18,7 @@ import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
class OrderablePlaylistSongAdapter( class OrderablePlaylistSongAdapter(
private val playlist: PlaylistEntity,
activity: FragmentActivity, activity: FragmentActivity,
dataSet: ArrayList<Song>, dataSet: ArrayList<Song>,
itemLayoutRes: Int, itemLayoutRes: Int,
@ -54,8 +57,8 @@ class OrderablePlaylistSongAdapter(
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(selection as ArrayList<PlaylistSong>) RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId))
.show(activity.supportFragmentManager, "ADD_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return return
} }
} }
@ -118,7 +121,7 @@ class OrderablePlaylistSongAdapter(
override fun onSongMenuItemClick(item: MenuItem): Boolean { override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_remove_from_playlist -> { R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(song as PlaylistSong) RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlist.playListId))
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST") .show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return true return true
} }

View File

@ -20,7 +20,6 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstant
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault 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.action.SwipeResultActionRemoveItem
import com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter( class PlayingQueueAdapter(
@ -153,8 +152,8 @@ class PlayingQueueAdapter(
mDragStateFlags = flags mDragStateFlags = flags
} }
override fun getSwipeableContainerView(): View? { override fun getSwipeableContainerView(): View {
return dummyContainer return dummyContainer!!
} }
} }
@ -165,18 +164,15 @@ class PlayingQueueAdapter(
private const val UP_NEXT = 2 private const val UP_NEXT = 2
} }
override fun onSwipeItem( override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? {
holder: ViewHolder?, return if (result == SwipeableItemConstants.RESULT_CANCELED) {
position: Int, @SwipeableItemResults result: Int
): SwipeResultAction {
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault() SwipeResultActionDefault()
} else { } else {
SwipedResultActionRemoveItem(this, position, activity) SwipedResultActionRemoveItem(this, position, activity)
} }
} }
override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int { override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int {
return if (onCheckCanStartDrag(holder!!, position, x, y)) { return if (onCheckCanStartDrag(holder!!, position, x, y)) {
SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
} else { } else {
@ -184,10 +180,10 @@ class PlayingQueueAdapter(
} }
} }
override fun onSwipeItemStarted(p0: ViewHolder?, p1: Int) { override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) {
} }
override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) { override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) {
} }
internal class SwipedResultActionRemoveItem( internal class SwipedResultActionRemoveItem(

View File

@ -0,0 +1,21 @@
package code.name.monkey.retromusic.db
import androidx.room.*
@Dao
interface BlackListStoreDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertBlacklistPath(blackListStoreEntities: List<BlackListStoreEntity>)
@Delete
suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
@Query("DELETE FROM BlackListStoreEntity")
suspend fun clearBlacklist()
@Query("SELECT * FROM BlackListStoreEntity")
fun blackListPaths(): List<BlackListStoreEntity>
}

View File

@ -0,0 +1,10 @@
package code.name.monkey.retromusic.db
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class BlackListStoreEntity(
@PrimaryKey
val path: String
)

View File

@ -0,0 +1,26 @@
package code.name.monkey.retromusic.db
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface HistoryDao {
companion object {
private const val HISTORY_LIMIT = 100
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSongInHistory(historyEntity: HistoryEntity)
@Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1")
suspend fun isSongPresentInHistory(songId: Int): 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: Int,
val title: String,
@ColumnInfo(name = "track_number")
val trackNumber: Int,
val year: Int,
val duration: Long,
val data: String,
@ColumnInfo(name = "date_modified")
val dateModified: Long,
@ColumnInfo(name = "album_id")
val albumId: Int,
@ColumnInfo(name = "album_name")
val albumName: String,
@ColumnInfo(name = "artist_id")
val artistId: Int,
@ColumnInfo(name = "artist_name")
val artistName: String,
val composer: String?,
@ColumnInfo(name = "album_artist")
val albumArtist: String?,
@ColumnInfo(name = "time_played")
val timePlayed: Long
)

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: Int): 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: Int)
@Query("UPDATE PlayCountEntity SET play_count = play_count + 1 WHERE id = :id")
fun updateQuantity(id: Int)
}

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: Int,
val title: String,
@ColumnInfo(name = "track_number")
val trackNumber: Int,
val year: Int,
val duration: Long,
val data: String,
@ColumnInfo(name = "date_modified")
val dateModified: Long,
@ColumnInfo(name = "album_id")
val albumId: Int,
@ColumnInfo(name = "album_name")
val albumName: String,
@ColumnInfo(name = "artist_id")
val artistId: Int,
@ColumnInfo(name = "artist_name")
val artistName: String,
val composer: String?,
@ColumnInfo(name = "album_artist")
val albumArtist: String?,
@ColumnInfo(name = "time_played")
val timePlayed: Long,
@ColumnInfo(name = "play_count")
var playCount: Int
)

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: Int, 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: Int)
@Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun deleteSongFromPlaylist(playlistId: Int, songId: Int)
@Transaction
@Query("SELECT * FROM PlaylistEntity")
suspend fun playlistsWithSongs(): List<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: Int, songId: Int): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId")
fun songsFromPlaylist(playlistId: Int): 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: Int): LiveData<List<SongEntity>>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongs(playlistId: Int): List<SongEntity>
}

View File

@ -0,0 +1,18 @@
package code.name.monkey.retromusic.db
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Entity
@Parcelize
class PlaylistEntity(
@ColumnInfo(name = "playlist_name")
val playlistName: String
) : Parcelable {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "playlist_id")
var playListId: Int = 0
}

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,40 @@
package code.name.monkey.retromusic.db
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(indices = [Index(value = ["playlist_creator_id", "id"], unique = true)])
class SongEntity(
@ColumnInfo(name = "playlist_creator_id")
val playlistCreatorId: Int,
val id: Int,
val title: String,
@ColumnInfo(name = "track_number")
val trackNumber: Int,
val year: Int,
val duration: Long,
val data: String,
@ColumnInfo(name = "date_modified")
val dateModified: Long,
@ColumnInfo(name = "album_id")
val albumId: Int,
@ColumnInfo(name = "album_name")
val albumName: String,
@ColumnInfo(name = "artist_id")
val artistId: Int,
@ColumnInfo(name = "artist_name")
val artistName: String,
val composer: String?,
@ColumnInfo(name = "album_artist")
val albumArtist: String?
) : Parcelable {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "song_key")
var songPrimaryKey: Long = 0
}

View File

@ -0,0 +1,95 @@
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: Int): List<SongEntity> {
return map {
it.toSongEntity(playlistId)
}
}
fun SongEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun PlayCountEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun HistoryEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun Song.toPlayCount(): PlayCountEntity {
return PlayCountEntity(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist,
System.currentTimeMillis(),
1
)
}
fun List<Song>.toSongsEntity(playlistEntity: PlaylistEntity): List<SongEntity> {
return map {
it.toSongEntity(playlistEntity.playListId)
}
}

View File

@ -1,77 +1,70 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.EXTRA_PLAYLISTS
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.toSongsEntity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistRepository import kotlinx.coroutines.Dispatchers
import code.name.monkey.retromusic.util.PlaylistsUtil import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class AddToPlaylistDialog : DialogFragment() { class AddToPlaylistDialog : DialogFragment() {
private val playlistRepository by inject<PlaylistRepository>() private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onCreateDialog(
savedInstanceState: Bundle? companion object {
): Dialog { fun create(playlistEntities: List<PlaylistEntity>, song: Song): AddToPlaylistDialog {
val playlists = playlistRepository.playlists() val list: MutableList<Song> = mutableListOf()
val playlistNames = mutableListOf<String>() list.add(song)
return create(playlistEntities, list)
}
fun create(playlistEntities: List<PlaylistEntity>, songs: List<Song>): AddToPlaylistDialog {
return AddToPlaylistDialog().apply {
arguments = bundleOf(
EXTRA_SONG to songs,
EXTRA_PLAYLISTS to playlistEntities
)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistEntities: List<PlaylistEntity> =
extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLISTS).value
val songs: List<Song> = extraNotNull<List<Song>>(EXTRA_SONG).value
val playlistNames: MutableList<String> = mutableListOf()
playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist)) playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist))
for (p in playlists) { for (entity: PlaylistEntity in playlistEntities) {
playlistNames.add(p.name) playlistNames.add(entity.playlistName)
} }
return materialDialog(R.string.add_playlist_title) return materialDialog(R.string.add_playlist_title)
.setItems(playlistNames.toTypedArray()) { _, which -> .setItems(playlistNames.toTypedArray()) { _, which ->
val songs = extraNotNull<ArrayList<Song>>(EXTRA_SONG).value
if (which == 0) { if (which == 0) {
CreatePlaylistDialog.create(songs) CreatePlaylistDialog.create(songs)
.show(requireActivity().supportFragmentManager, "ADD_TO_PLAYLIST") .show(requireActivity().supportFragmentManager, "Dialog")
} else { } else {
PlaylistsUtil.addToPlaylist( lifecycleScope.launch(Dispatchers.IO) {
requireContext(), val songEntities: List<SongEntity> =
songs, songs.toSongsEntity(playlistEntities[which - 1])
playlists[which - 1].id, libraryViewModel.insertSongs(songEntities)
true libraryViewModel.forceReload(Playlists)
) }
} }
dismiss() dismiss()
} }
.create().colorButtons() .create().colorButtons()
} }
companion object {
fun create(song: Song): AddToPlaylistDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
fun create(songs: List<Song>): AddToPlaylistDialog {
val dialog = AddToPlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
} }

View File

@ -1,88 +1,74 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.* import kotlinx.android.synthetic.main.dialog_playlist.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class CreatePlaylistDialog : DialogFragment() { class CreatePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams") companion object {
override fun onCreateDialog( fun create(song: Song): CreatePlaylistDialog {
savedInstanceState: Bundle? val list = mutableListOf<Song>()
): Dialog { list.add(song)
return create(list)
}
fun create(songs: List<Song>): CreatePlaylistDialog {
return CreatePlaylistDialog().apply {
arguments = bundleOf(EXTRA_SONG to songs)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null) val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null)
val songs: List<Song> = extra<List<Song>>(EXTRA_SONG).value ?: emptyList()
val playlistView: TextInputEditText = view.actionNewPlaylist val playlistView: TextInputEditText = view.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer
MaterialUtil.setTint(playlistContainer, false)
return materialDialog(R.string.new_playlist_title) return materialDialog(R.string.new_playlist_title)
.setView(view) .setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton( .setPositiveButton(
R.string.create_action R.string.create_action
) { _, _ -> ) { _, _ ->
val extra = extraNotNull<ArrayList<Song>>(EXTRA_SONG)
val playlistName = playlistView.text.toString() val playlistName = playlistView.text.toString()
if (!TextUtils.isEmpty(playlistName)) { if (!TextUtils.isEmpty(playlistName)) {
val playlistId = PlaylistsUtil.createPlaylist( lifecycleScope.launch(Dispatchers.IO) {
requireContext(), if (libraryViewModel.checkPlaylistExists(playlistName).isEmpty()) {
playlistView.text.toString() val playlistId: Long =
) libraryViewModel.createPlaylist(PlaylistEntity(playlistName))
if (playlistId != -1) { libraryViewModel.insertSongs(songs.map { it.toSongEntity(playlistId.toInt()) })
PlaylistsUtil.addToPlaylist(requireContext(), extra.value, playlistId, true) libraryViewModel.forceReload(Playlists)
} else {
Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT)
.show()
}
} }
} else {
playlistContainer.error = "Playlist is can't be empty"
} }
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
@JvmOverloads
@JvmStatic
fun create(song: Song? = null): CreatePlaylistDialog {
val list = ArrayList<Song>()
if (song != null) {
list.add(song)
}
return create(list)
}
@JvmStatic
fun create(songs: ArrayList<Song>): CreatePlaylistDialog {
val dialog = CreatePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, songs)
dialog.arguments = args
return dialog
}
}
} }

View File

@ -1,35 +1,41 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.fragments.ReloadType
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeletePlaylistDialog : DialogFragment() { class DeletePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
companion object {
fun create(playlist: PlaylistEntity): DeletePlaylistDialog {
val list = mutableListOf<PlaylistEntity>()
list.add(playlist)
return create(list)
}
fun create(playlists: List<PlaylistEntity>): DeletePlaylistDialog {
return DeletePlaylistDialog().apply {
arguments = bundleOf(EXTRA_PLAYLIST to playlists)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlists = extraNotNull<List<Playlist>>(EXTRA_PLAYLIST).value val playlists = extraNotNull<List<PlaylistEntity>>(EXTRA_PLAYLIST).value
val title: Int val title: Int
val message: CharSequence val message: CharSequence
//noinspection ConstantConditions //noinspection ConstantConditions
@ -42,7 +48,7 @@ class DeletePlaylistDialog : DialogFragment() {
} else { } else {
title = R.string.delete_playlist_title title = R.string.delete_playlist_title
message = HtmlCompat.fromHtml( message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_playlist_x), playlists[0].name), String.format(getString(R.string.delete_playlist_x), playlists[0].playlistName),
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
} }
@ -52,26 +58,12 @@ class DeletePlaylistDialog : DialogFragment() {
.setMessage(message) .setMessage(message)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ -> .setPositiveButton(R.string.action_delete) { _, _ ->
PlaylistsUtil.deletePlaylists(requireContext(), playlists) libraryViewModel.deleteSongsFromPlaylist(playlists)
libraryViewModel.deleteRoomPlaylist(playlists)
libraryViewModel.forceReload(ReloadType.Playlists)
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
fun create(playlist: Playlist): DeletePlaylistDialog {
val list = ArrayList<Playlist>()
list.add(playlist)
return create(list)
}
fun create(playlist: ArrayList<Playlist>): DeletePlaylistDialog {
val dialog = DeletePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_PLAYLIST, playlist)
dialog.arguments = args
return dialog
}
}
} }

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.SAFUtil;
/**
* Created by hemanths on 2019-07-31.
*/
public class DeleteSongsAsyncTask extends DialogAsyncTask<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 code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.SAFUtil import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeleteSongsDialog : DialogFragment() { class DeleteSongsDialog : DialogFragment() {
@JvmField private val libraryViewModel by sharedViewModel<LibraryViewModel>()
var currentSong: Song? = null
@JvmField
var songsToRemove: List<Song>? = null
private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
var title = 0
var message: CharSequence = ""
if (songs.size > 1) {
title = R.string.delete_songs_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} else {
title = R.string.delete_song_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_song_x), songs[0].title),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
return materialDialog(title)
.setMessage(message)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ ->
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
MusicPlayerRemote.playNextSong()
}
songsToRemove = songs
deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog)
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null))
}
.create()
.colorButtons()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE,
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (deleteSongsAsyncTask != null) {
deleteSongsAsyncTask?.cancel(true)
}
deleteSongsAsyncTask = DeleteSongsAsyncTask(this)
deleteSongsAsyncTask?.execute(
DeleteSongsAsyncTask.LoadingInfo(
requestCode,
resultCode,
data
)
)
}
}
}
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable {
dismiss()
})
}
companion object { companion object {
fun create(song: Song): DeleteSongsDialog { fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>() val list = ArrayList<Song>()
list.add(song) list.add(song)
@ -119,5 +33,38 @@ class DeleteSongsDialog : DialogFragment() {
return dialog return dialog
} }
} }
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
val pair = if (songs.size > 1) {
Pair(
R.string.delete_songs_title,
HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
} else {
Pair(
R.string.delete_song_title,
HtmlCompat.fromHtml(
String.format(getString(R.string.delete_song_x), songs[0].title),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
}
return materialDialog(pair.first)
.setMessage(pair.second)
.setCancelable(false)
.setPositiveButton(R.string.action_delete) { _, _ ->
if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) {
MusicPlayerRemote.playNextSong()
}
MusicUtil.deleteTracks(requireActivity(), songs)
libraryViewModel.deleteTracks(songs)
}
.create()
.colorButtons()
}
}

View File

@ -0,0 +1,24 @@
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ImportPlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return materialDialog(R.string.import_playlist)
.setMessage(R.string.import_playlist_message)
.setPositiveButton(R.string.import_label) { _, _ ->
libraryViewModel.importPlaylists()
}
.create()
.colorButtons()
}
}

View File

@ -0,0 +1,60 @@
package code.name.monkey.retromusic.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.accentTextColor
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.repository.Repository
import kotlinx.android.synthetic.main.lyrics_dialog.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
class LyricsDialog : DialogFragment() {
override fun getTheme(): Int {
return R.style.MaterialAlertDialogTheme
}
private val repository by inject<Repository>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.lyrics_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val song = MusicPlayerRemote.currentSong
dialogTitle.text = song.title
syncedLyrics.accentTextColor()
lifecycleScope.launch(IO) {
val result: Result<String> = repository.lyrics(
song.artistName,
song.title
)
withContext(Main) {
when (result) {
is Result.Error -> progressBar.hide()
is Result.Loading -> println("Loading")
is Result.Success -> {
progressBar.hide()
lyricsText.text = result.data
}
}
}
}
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.string
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.util.PlaylistsUtil
class RemoveFromPlaylistDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = requireArguments().getParcelableArrayList<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(string.remove_x_songs_from_playlist), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} else {
title = R.string.remove_song_from_playlist_title
message = HtmlCompat.fromHtml(
String.format(
getString(string.remove_song_x_from_playlist),
songs[0].title
),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
}
return materialDialog(title)
.setMessage(message)
.setPositiveButton(R.string.remove_action) { _, _ ->
PlaylistsUtil.removeFromPlaylist(
requireContext(),
songs as MutableList<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 code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore.Audio.Playlists.Members.PLAYLIST_ID
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil import code.name.monkey.retromusic.EXTRA_PLAYLIST_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RenamePlaylistDialog : DialogFragment() { class RenamePlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@SuppressLint("InflateParams") companion object {
override fun onCreateDialog( fun create(playlistEntity: PlaylistEntity): RenamePlaylistDialog {
savedInstanceState: Bundle? return RenamePlaylistDialog().apply {
): Dialog { arguments = bundleOf(
val layout = LayoutInflater.from(requireContext()) EXTRA_PLAYLIST_ID to playlistEntity
.inflate(R.layout.dialog_playlist, null) )
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistEntity = extraNotNull<PlaylistEntity>(EXTRA_PLAYLIST_ID).value
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_playlist, null)
val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist) val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist)
val nameContainer: TextInputLayout = val nameContainer: TextInputLayout = layout.findViewById(R.id.actionNewPlaylistContainer)
layout.findViewById(R.id.actionNewPlaylistContainer) nameContainer.accentColor()
MaterialUtil.setTint(nameContainer, false) inputEditText.setText(playlistEntity.playlistName)
return materialDialog(R.string.rename_playlist_title) return materialDialog(R.string.rename_playlist_title)
.setView(layout) .setView(layout)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_rename) { _, _ -> .setPositiveButton(R.string.action_rename) { _, _ ->
val name = inputEditText.text.toString() val name = inputEditText.text.toString()
if (name.isNotEmpty()) { if (name.isNotEmpty()) {
PlaylistsUtil.renamePlaylist( libraryViewModel.renameRoomPlaylist(playlistEntity.playListId, name)
requireContext(), libraryViewModel.forceReload(ReloadType.Playlists)
extraNotNull<Long>(PLAYLIST_ID).value, } else {
name nameContainer.error = "Playlist name should'nt be empty"
)
} }
} }
.create() .create()
.colorButtons() .colorButtons()
} }
companion object {
fun create(playlistId: Long): RenamePlaylistDialog {
val dialog = RenamePlaylistDialog()
val args = Bundle()
args.putLong(PLAYLIST_ID, playlistId)
dialog.arguments = args
return dialog
}
}
} }

View File

@ -1,13 +0,0 @@
package code.name.monkey.retromusic.dialogs
import android.content.Context
import android.widget.ArrayAdapter
import code.name.monkey.retromusic.R
class RetroSingleCheckedListAdapter(
context: Context,
resource: Int = R.layout.dialog_list_item,
objects: MutableList<String>
) : ArrayAdapter<String>(context, resource, objects) {
}

View File

@ -0,0 +1,55 @@
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.util.PlaylistsUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SavePlaylistDialog : DialogFragment() {
companion object {
fun create(playlistWithSongs: PlaylistWithSongs): SavePlaylistDialog {
return SavePlaylistDialog().apply {
arguments = bundleOf(
EXTRA_PLAYLIST to playlistWithSongs
)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch(Dispatchers.IO) {
val playlistWithSongs: PlaylistWithSongs =
extraNotNull<PlaylistWithSongs>(EXTRA_PLAYLIST).value
val file = PlaylistsUtil.savePlaylistWithSongs(requireContext(), playlistWithSongs)
withContext(Dispatchers.Main) {
Toast.makeText(
requireContext(),
String.format(App.getContext().getString(R.string.saved_playlist_to), file),
Toast.LENGTH_LONG
).show()
dismiss()
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return materialDialog(R.string.save_playlist_title)
.setView(R.layout.loading)
.create().colorButtons()
}
}

View File

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

View File

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

View File

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

View File

@ -18,12 +18,18 @@ import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.widget.Button import android.widget.Button
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.SeekBar import android.widget.SeekBar
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.CheckResult
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
@ -33,6 +39,8 @@ import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.ProgressIndicator
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@ -91,6 +99,10 @@ fun Button.accentTextColor() {
setTextColor(ThemeStore.accentColor(App.getContext())) setTextColor(ThemeStore.accentColor(App.getContext()))
} }
fun MaterialButton.accentTextColor() {
setTextColor(ThemeStore.accentColor(App.getContext()))
}
fun SeekBar.applyColor(@ColorInt color: Int) { fun SeekBar.applyColor(@ColorInt color: Int) {
thumbTintList = ColorStateList.valueOf(color) thumbTintList = ColorStateList.valueOf(color)
progressTintList = ColorStateList.valueOf(color) progressTintList = ColorStateList.valueOf(color)
@ -107,6 +119,15 @@ fun ExtendedFloatingActionButton.accentColor() {
iconTint = textColorStateList iconTint = textColorStateList
} }
fun FloatingActionButton.accentColor() {
val color = ThemeStore.accentColor(context)
val textColor = MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))
val colorStateList = ColorStateList.valueOf(color)
val textColorStateList = ColorStateList.valueOf(textColor)
backgroundTintList = colorStateList
imageTintList = textColorStateList
}
fun MaterialButton.applyColor(color: Int) { fun MaterialButton.applyColor(color: Int) {
val backgroundColorStateList = ColorStateList.valueOf(color) val backgroundColorStateList = ColorStateList.valueOf(color)
val textColorColorStateList = ColorStateList.valueOf( val textColorColorStateList = ColorStateList.valueOf(
@ -120,6 +141,12 @@ fun MaterialButton.applyColor(color: Int) {
iconTint = textColorColorStateList iconTint = textColorColorStateList
} }
fun MaterialButton.applyOutlineColor(color: Int) {
val textColorColorStateList = ColorStateList.valueOf(color)
setTextColor(textColorColorStateList)
iconTint = textColorColorStateList
}
fun TextInputLayout.accentColor() { fun TextInputLayout.accentColor() {
val accentColor = ThemeStore.accentColor(context) val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor) val colorState = ColorStateList.valueOf(accentColor)
@ -128,6 +155,39 @@ fun TextInputLayout.accentColor() {
isHintAnimationEnabled = true isHintAnimationEnabled = true
} }
fun ProgressIndicator.accentColor() {
val accentColor = ThemeStore.accentColor(context)
indicatorColors = intArrayOf(accentColor)
trackColor = ColorUtil.withAlpha(accentColor, 0.2f)
}
fun ProgressIndicator.applyColor(color: Int) {
indicatorColors = intArrayOf(color)
trackColor = ColorUtil.withAlpha(color, 0.2f)
}
fun TextInputEditText.accentColor() { fun TextInputEditText.accentColor() {
} }
fun AppCompatImageView.accentColor(): Int {
return ThemeStore.accentColor(context)
}
@CheckResult
fun Drawable.tint(@ColorInt color: Int): Drawable {
val tintedDrawable = DrawableCompat.wrap(this).mutate()
DrawableCompat.setTint(this, color)
return tintedDrawable
}
@CheckResult
fun Drawable.tint(context: Context, @ColorRes color: Int): Drawable {
return tint(context.getColorCompat(color))
}
@ColorInt
fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes)
}

View File

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

View File

@ -2,12 +2,15 @@ package code.name.monkey.retromusic.extensions
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.PowerManager import android.os.PowerManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@ -51,7 +54,8 @@ val FragmentManager.currentNavigationFragment: Fragment?
get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()
fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? { fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? {
val navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(navHostId) as NavHostFragment val navHostFragment: NavHostFragment =
supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
navHostFragment.targetFragment navHostFragment.targetFragment
return navHostFragment.childFragmentManager.fragments.first() return navHostFragment.childFragmentManager.fragments.first()
} }
@ -72,4 +76,12 @@ fun Fragment.showToast(@StringRes stringRes: Int) {
fun Fragment.showToast(message: String) { fun Fragment.showToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}
fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable {
return AppCompatResources.getDrawable(this, drawableRes)!!
}
fun Fragment.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable {
return AppCompatResources.getDrawable(requireContext(), drawableRes)!!
} }

View File

@ -14,13 +14,10 @@
package code.name.monkey.retromusic.extensions package code.name.monkey.retromusic.extensions
import android.content.res.ColorStateList
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.SeekBar
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper

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,8 @@ package code.name.monkey.retromusic.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -10,15 +12,14 @@ import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.db.toSong
import code.name.monkey.retromusic.fragments.albums.AlbumClickListener import code.name.monkey.retromusic.fragments.albums.AlbumClickListener
import code.name.monkey.retromusic.fragments.artists.ArtistClickListener import code.name.monkey.retromusic.fragments.artists.ArtistClickListener
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.* import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -34,6 +35,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
when (args.type) { when (args.type) {
TOP_ARTISTS -> { TOP_ARTISTS -> {
loadArtists(R.string.top_artists, TOP_ARTISTS) loadArtists(R.string.top_artists, TOP_ARTISTS)
@ -47,32 +49,88 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
loadAlbums(R.string.recent_albums, RECENT_ALBUMS) loadAlbums(R.string.recent_albums, RECENT_ALBUMS)
} }
FAVOURITES -> { FAVOURITES -> loadFavorite()
loadFavorite() HISTORY_PLAYLIST -> loadHistory()
} LAST_ADDED_PLAYLIST -> lastAddedSongs()
TOP_PLAYED_PLAYLIST -> topPlayed()
} }
} }
private fun lastAddedSongs() {
toolbar.setTitle(R.string.last_added)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.recentSongs()
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun topPlayed() {
toolbar.setTitle(R.string.my_top_tracks)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.playCountSongs().map {
it.toSong()
}
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun loadHistory() {
toolbar.setTitle(R.string.history)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
repository.observableHistorySongs().observe(viewLifecycleOwner, Observer {
val songs = it.map { historyEntity -> historyEntity.toSong() }
songAdapter.swapDataSet(songs)
})
}
private fun loadFavorite() { private fun loadFavorite() {
toolbar.setTitle(R.string.favorites) toolbar.setTitle(R.string.favorites)
CoroutineScope(IO).launch { val songAdapter = SongAdapter(
val songs = repository.favoritePlaylistHome() requireActivity(),
withContext(Main) { mutableListOf(),
recyclerView.apply { R.layout.item_list, null
adapter = SongAdapter( )
requireActivity(), recyclerView.apply {
songs.arrayList as MutableList<Song>, adapter = songAdapter
R.layout.item_list, null layoutManager = linearLayoutManager()
)
layoutManager = linearLayoutManager()
}
}
} }
repository.favorites().observe(viewLifecycleOwner, Observer {
println(it.size)
val songs = it.map { songEntity -> songEntity.toSong() }
songAdapter.swapDataSet(songs)
})
} }
private fun loadArtists(title: Int, type: Int) { private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title) toolbar.setTitle(title)
CoroutineScope(IO).launch { lifecycleScope.launch(IO) {
val artists = val artists =
if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists() if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists()
withContext(Main) { withContext(Main) {
@ -86,7 +144,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
private fun loadAlbums(title: Int, type: Int) { private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title) toolbar.setTitle(title)
CoroutineScope(IO).launch { lifecycleScope.launch(IO) {
val albums = val albums =
if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums() if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums()
withContext(Main) { withContext(Main) {

View File

@ -4,83 +4,129 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.fragments.ReloadType.* import code.name.monkey.retromusic.fragments.ReloadType.*
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LibraryViewModel( class LibraryViewModel(
private val realRepository: RealRepository private val repository: RealRepository
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>() private val paletteColor = MutableLiveData<Int>()
private val albums = MutableLiveData<List<Album>>() private val albums = MutableLiveData<List<Album>>()
private val songs = MutableLiveData<List<Song>>() private val songs = MutableLiveData<List<Song>>()
private val artists = MutableLiveData<List<Artist>>() private val artists = MutableLiveData<List<Artist>>()
private val playlists = MutableLiveData<List<Playlist>>() private val playlists = MutableLiveData<List<PlaylistWithSongs>>()
private val legacyPlaylists = MutableLiveData<List<Playlist>>()
private val genres = MutableLiveData<List<Genre>>() private val genres = MutableLiveData<List<Genre>>()
private val home = MutableLiveData<List<Home>>() private val home = MutableLiveData<List<Home>>()
val paletteColorLiveData: LiveData<Int> = paletteColor val paletteColorLiveData: LiveData<Int> = paletteColor
val homeLiveData: LiveData<List<Home>> = home
val albumsLiveData: LiveData<List<Album>> = albums
val songsLiveData: LiveData<List<Song>> = songs
val artistsLiveData: LiveData<List<Artist>> = artists
val playlisitsLiveData: LiveData<List<Playlist>> = playlists
val genresLiveData: LiveData<List<Genre>> = genres
init { init {
viewModelScope.launch { fetchHomeSections()
loadLibraryContent() }
private fun loadLibraryContent() = viewModelScope.launch(IO) {
fetchHomeSections()
fetchSongs()
fetchAlbums()
fetchArtists()
fetchGenres()
fetchPlaylists()
}
fun getSongs(): LiveData<List<Song>> {
fetchSongs()
return songs
}
fun getAlbums(): LiveData<List<Album>> {
fetchAlbums()
return albums
}
fun getArtists(): LiveData<List<Artist>> {
fetchArtists()
return artists
}
fun getPlaylists(): LiveData<List<PlaylistWithSongs>> {
fetchPlaylists()
return playlists
}
fun getLegacyPlaylist(): LiveData<List<Playlist>> {
fetchLegacyPlaylist()
return legacyPlaylists
}
fun getGenre(): LiveData<List<Genre>> {
fetchGenres()
return genres
}
fun getHome(): LiveData<List<Home>> {
return home
}
private fun fetchSongs() {
viewModelScope.launch(IO) {
songs.postValue(repository.allSongs())
} }
} }
private fun loadLibraryContent() = viewModelScope.launch { private fun fetchAlbums() {
songs.value = loadSongs.await() viewModelScope.launch(IO) {
albums.value = loadAlbums.await() albums.postValue(repository.fetchAlbums())
artists.value = loadArtists.await() }
playlists.value = loadPlaylists.await()
genres.value = loadGenres.await()
home.value = loadHome.await()
} }
private val loadHome: Deferred<List<Home>> private fun fetchArtists() {
get() = viewModelScope.async { realRepository.homeSections() } viewModelScope.launch(IO) {
artists.postValue(repository.fetchArtists())
private val loadSongs: Deferred<List<Song>>
get() = viewModelScope.async(IO) { realRepository.allSongs() }
private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) {
realRepository.allAlbums()
} }
}
private val loadArtists: Deferred<List<Artist>> private fun fetchPlaylists() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.albumArtists() playlists.postValue(repository.fetchPlaylistWithSongs())
} }
}
private val loadPlaylists: Deferred<List<Playlist>> private fun fetchLegacyPlaylist() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.allPlaylists() legacyPlaylists.postValue(repository.fetchLegacyPlaylist())
} }
}
private val loadGenres: Deferred<List<Genre>> private fun fetchGenres() {
get() = viewModelScope.async(IO) { viewModelScope.launch(IO) {
realRepository.allGenres() genres.postValue(repository.fetchGenres())
} }
}
private fun fetchHomeSections() {
viewModelScope.launch(IO) {
home.postValue(repository.homeSections())
}
}
fun forceReload(reloadType: ReloadType) = viewModelScope.launch { fun forceReload(reloadType: ReloadType) = viewModelScope.launch {
when (reloadType) { when (reloadType) {
Songs -> songs.value = loadSongs.await() Songs -> fetchSongs()
Albums -> albums.value = loadAlbums.await() Albums -> fetchAlbums()
Artists -> artists.value = loadArtists.await() Artists -> fetchArtists()
HomeSections -> songs.value = loadSongs.await() HomeSections -> fetchHomeSections()
Playlists -> fetchPlaylists()
Genres -> fetchGenres()
} }
} }
@ -89,11 +135,10 @@ class LibraryViewModel(
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadLibraryContent()
println("onMediaStoreChanged") println("onMediaStoreChanged")
loadLibraryContent()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
println("onServiceConnected") println("onServiceConnected")
} }
@ -108,6 +153,7 @@ class LibraryViewModel(
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
println("onPlayingMetaChanged") println("onPlayingMetaChanged")
} }
override fun onPlayStateChanged() { override fun onPlayStateChanged() {
@ -122,11 +168,76 @@ class LibraryViewModel(
println("onShuffleModeChanged") println("onShuffleModeChanged")
} }
fun shuffleSongs() = viewModelScope.launch(IO) {
val songs = repository.allSongs()
MusicPlayerRemote.openAndShuffleQueue(
songs,
true
)
}
fun renameRoomPlaylist(playListId: Int, name: String) = viewModelScope.launch(IO) {
repository.renameRoomPlaylist(playListId, name)
}
fun deleteSongsInPlaylist(songs: List<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: Int) = repository.albumById(id)
suspend fun artistById(id: Int) = repository.artistById(id)
suspend fun favoritePlaylist() = repository.favoritePlaylist()
suspend fun isFavoriteSong(song: SongEntity) = repository.isFavoriteSong(song)
suspend fun insertSongs(songs: List<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(playlist.name))
val songEntities = playlist.getSongs().map {
it.toSongEntity(playListId.toInt())
}
repository.insertSongs(songEntities)
}
forceReload(Playlists)
}
}
fun deleteTracks(songs: List<Song>) = viewModelScope.launch(IO) {
repository.deleteSongs(songs)
fetchPlaylists()
loadLibraryContent()
}
} }
enum class ReloadType { enum class ReloadType {
Songs, Songs,
Albums, Albums,
Artists, Artists,
HomeSections HomeSections,
Playlists,
Genres,
} }

View File

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

View File

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

View File

@ -6,7 +6,9 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
@ -25,16 +27,20 @@ import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.applyColor import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.AlbumGlideRequest import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
@ -42,6 +48,10 @@ import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_album_content.* import kotlinx.android.synthetic.main.fragment_album_content.*
import kotlinx.android.synthetic.main.fragment_album_details.* import kotlinx.android.synthetic.main.fragment_album_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
@ -66,23 +76,13 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
mainActivity.addMusicServiceEventListener(detailsViewModel) mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar) mainActivity.setSupportActionBar(toolbar)
toolbar.title = " "
toolbar.title = null
postponeEnterTransition() postponeEnterTransition()
detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer { detailsViewModel.getAlbum().observe(viewLifecycleOwner, Observer {
showAlbum(it)
startPostponedEnterTransition() startPostponedEnterTransition()
showAlbum(it)
}) })
detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer {
loadArtistImage(it)
})
detailsViewModel.getMoreAlbums().observe(viewLifecycleOwner, Observer {
moreAlbums(it)
})
detailsViewModel.getAlbumInfo().observe(viewLifecycleOwner, Observer {
aboutAlbum(it)
})
setupRecyclerView() setupRecyclerView()
artistImage.setOnClickListener { artistImage.setOnClickListener {
requireActivity().findNavController(R.id.fragment_container) requireActivity().findNavController(R.id.fragment_container)
@ -140,14 +140,12 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
this.album = album this.album = album
albumTitle.text = album.title albumTitle.text = album.title
val songText = val songText = resources.getQuantityString(
resources.getQuantityString( R.plurals.albumSongs,
R.plurals.albumSongs, album.songCount,
album.songCount, album.songCount
album.songCount )
)
songTitle.text = songText songTitle.text = songText
if (MusicUtil.getYearString(album.year) == "-") { if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format( albumText.text = String.format(
"%s • %s", "%s • %s",
@ -162,10 +160,25 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs)) MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
) )
} }
loadAlbumCover() loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
detailsViewModel.loadArtist(album.artistId) detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer {
detailsViewModel.loadAlbumInfo(album) loadArtistImage(it)
})
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result ->
when (result) {
is Result.Loading -> {
println("Loading")
}
is Result.Error -> {
println("Error")
}
is Result.Success -> {
aboutAlbum(result.data)
}
}
})
} }
private fun moreAlbums(albums: List<Album>) { private fun moreAlbums(albums: List<Album>) {
@ -191,7 +204,10 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
aboutAlbumTitle.show() aboutAlbumTitle.show()
aboutAlbumTitle.text = aboutAlbumTitle.text =
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name) String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
aboutAlbumText.text = lastFmAlbum.album.wiki.content aboutAlbumText.text = HtmlCompat.fromHtml(
lastFmAlbum.album.wiki.content,
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} }
if (lastFmAlbum.album.listeners.isNotEmpty()) { if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show() listeners.show()
@ -206,7 +222,11 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun loadArtistImage(artist: Artist) { private fun loadArtistImage(artist: Artist) {
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, Observer {
moreAlbums(it)
})
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.generatePalette(requireContext()) .generatePalette(requireContext())
.build() .build()
.dontAnimate() .dontAnimate()
@ -217,24 +237,21 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
}) })
} }
private fun loadAlbumCover() { private fun loadAlbumCover(album: Album) {
AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(requireContext()), album.safeGetFirstSong())
.checkIgnoreMediaStore(requireContext()) .checkIgnoreMediaStore()
.ignoreMediaStore(PreferenceUtil.isIgnoreMediaStoreArtwork)
.generatePalette(requireContext()) .generatePalette(requireContext())
.build() .build()
.dontAnimate() .into(object : SingleColorTarget(image) {
.dontTransform() override fun onColorReady(color: Int) {
.into(object : RetroMusicColoredTarget(image) { setColors(color)
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors)
} }
}) })
} }
private fun setColors(color: MediaNotificationProcessor) { private fun setColors(color: Int) {
shuffleAction.applyColor(color.backgroundColor) shuffleAction.applyColor(color)
playAction.applyColor(color.backgroundColor) playAction.applyOutlineColor(color)
} }
override fun onAlbumClick(albumId: Int, view: View) { override fun onAlbumClick(albumId: Int, view: View) {
@ -275,7 +292,13 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
return true return true
} }
R.id.action_add_to_playlist -> { R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(childFragmentManager, "ADD_PLAYLIST") lifecycleScope.launch(Dispatchers.IO) {
val playlists = get<RealRepository>().fetchPlaylists()
withContext(Dispatchers.Main) {
AddToPlaylistDialog.create(playlists, songs)
.show(childFragmentManager, "ADD_PLAYLIST")
}
}
return true return true
} }
R.id.action_delete_from_device -> { R.id.action_delete_from_device -> {

View File

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

View File

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

View File

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

View File

@ -1,51 +1,37 @@
package code.name.monkey.retromusic.fragments.artists package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.liveData
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.network.model.LastFmArtist
import kotlinx.coroutines.Deferred import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private val artistId: Int private val artistId: Int
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?> fun getArtist(): LiveData<Artist> = liveData(IO) {
get() = viewModelScope.async(Dispatchers.IO) { val artist = realRepository.artistById(artistId)
realRepository.artistById(artistId) emit(artist)
}
private val _artist = MutableLiveData<Artist>()
private val _lastFmArtist = MutableLiveData<LastFmArtist>()
fun getArtist(): LiveData<Artist> = _artist
fun getArtistInfo(): LiveData<LastFmArtist> = _lastFmArtist
init {
loadArtistDetails()
} }
private fun loadArtistDetails() = viewModelScope.launch { fun getArtistInfo(
val artist = name: String,
loadArtistDetailsAsync.await() ?: throw NullPointerException("Album couldn't found") lang: String?,
_artist.postValue(artist) cache: String?
} ): LiveData<Result<LastFmArtist>> = liveData(IO) {
emit(Result.Loading)
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch {
val info = realRepository.artistInfo(name, lang, cache) val info = realRepository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info) emit(info)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadArtistDetails() getArtist()
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View File

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

View File

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

View File

@ -290,7 +290,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
} }
} }
if (startIndex > -1) { if (startIndex > -1) {
MusicPlayerRemote.INSTANCE.openQueue(songs, startIndex, true); MusicPlayerRemote.openQueue(songs, startIndex, true);
} else { } else {
final File finalFile = file1; final File finalFile = file1;
Snackbar.make(coordinatorLayout, Html.fromHtml( Snackbar.make(coordinatorLayout, Html.fromHtml(

View File

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

View File

@ -21,18 +21,12 @@ import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.GenreAdapter import code.name.monkey.retromusic.adapter.GenreAdapter
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks
class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(), class GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>() {
MainActivityFragmentCallbacks {
override fun handleBackPress(): Boolean {
return false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.genresLiveData.observe(viewLifecycleOwner, Observer { libraryViewModel.getGenre().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
@ -18,10 +17,8 @@ import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
@ -62,14 +59,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null private var playingQueueAdapter: PlayingQueueAdapter? = null
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var linearLayoutManager: LinearLayoutManager
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
playerQueueSheet.setPadding( playerQueueSheet.setPadding(
playerQueueSheet.paddingLeft, playerQueueSheet.paddingLeft,
(slideOffset * status_bar.height).toInt(), (slideOffset * status_bar.height).toInt(),
@ -79,18 +73,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) { when (newState) {
BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> { BottomSheetBehavior.STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false) mainActivity.getBottomSheetBehavior().setAllowDragging(false)
} }
BottomSheetBehavior.STATE_COLLAPSED -> { BottomSheetBehavior.STATE_COLLAPSED -> {
resetToCurrentPosition() resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true) mainActivity.getBottomSheetBehavior().setAllowDragging(true)
} }
else -> { else -> {
activity.getBottomSheetBehavior().setAllowDragging(true) mainActivity.getBottomSheetBehavior().setAllowDragging(true)
} }
} }
} }
@ -139,8 +132,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun setupSheet() { private fun setupSheet() {
getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList) getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList)
playerQueueSheet.setOnTouchListener { _, _ -> playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior() mainActivity.getBottomSheetBehavior().setAllowDragging(false)
.setAllowDragging(false)
getQueuePanel().setAllowDragging(true) getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false return@setOnTouchListener false
} }
@ -159,7 +151,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
recyclerViewDragDropManager?.cancelDrag() recyclerViewDragDropManager?.cancelDrag()
super.onPause() super.onPause()
progressViewUpdateHelper.stop() progressViewUpdateHelper.stop()
} }
override fun playerToolbar(): Toolbar? { override fun playerToolbar(): Toolbar? {
@ -224,9 +215,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun toggleFavorite(song: Song) { override fun toggleFavorite(song: Song) {
super.toggleFavorite(song) super.toggleFavorite(song)
MusicUtil.toggleFavorite(requireContext(), song)
if (song.id == MusicPlayerRemote.currentSong.id) { if (song.id == MusicPlayerRemote.currentSong.id) {
updateFavorite() updateIsFavorite()
} }
} }
@ -274,6 +264,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onQueueChanged() { override fun onQueueChanged() {
super.onQueueChanged() super.onQueueChanged()
updateLabel() updateLabel()
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue)
} }
private fun updateSong() { private fun updateSong() {
@ -372,7 +363,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} else { } else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
nextSong.apply { nextSong.apply {
text = "Next: $title" text = title
show() show()
} }
} }
@ -478,36 +469,4 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
@SuppressLint("StaticFieldLeak")
private fun updateFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask?.cancel(false)
}
updateIsFavoriteTask =
object : AsyncTask<Song, Void, Boolean>() {
override fun doInBackground(vararg params: Song): Boolean? {
val activity = activity
return if (activity != null) {
MusicUtil.isFavorite(requireActivity(), params[0])
} else {
cancel(false)
null
}
}
override fun onPostExecute(isFavorite: Boolean?) {
val activity = activity
if (activity != null) {
val res = if (isFavorite!!)
R.drawable.ic_favorite
else
R.drawable.ic_favorite_border
val drawable = TintHelper.createTintedDrawable(activity, res, Color.WHITE)
songFavourite?.setImageDrawable(drawable)
}
}
}.execute(MusicPlayerRemote.currentSong)
}
} }

View File

@ -0,0 +1,72 @@
package code.name.monkey.retromusic.fragments.playlists
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.playlist.LegacyPlaylistAdapter
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment
import code.name.monkey.retromusic.model.Playlist
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
class ImportPlaylistFragment :
AbsRecyclerViewFragment<LegacyPlaylistAdapter, LinearLayoutManager>(),
LegacyPlaylistAdapter.PlaylistClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
libraryViewModel.getLegacyPlaylist().observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty())
adapter?.swapData(it)
else
adapter?.swapData(listOf())
})
}
override fun createLayoutManager(): LinearLayoutManager {
return LinearLayoutManager(requireContext())
}
override fun createAdapter(): LegacyPlaylistAdapter {
return LegacyPlaylistAdapter(
requireActivity(),
ArrayList(),
R.layout.item_list_no_image,
this
)
}
override fun onPlaylistClick(playlist: Playlist) {
Toast.makeText(requireContext(), "Importing ${playlist.name}", Toast.LENGTH_LONG).show()
lifecycleScope.launch(IO) {
if (playlist.name.isNotEmpty()) {
if (libraryViewModel.checkPlaylistExists(playlist.name).isEmpty()) {
val playlistId: Long =
libraryViewModel.createPlaylist(PlaylistEntity(playlist.name))
libraryViewModel.insertSongs(playlist.getSongs().map {
it.toSongEntity(playlistId.toInt())
})
libraryViewModel.forceReload(Playlists)
} else {
Toast.makeText(requireContext(), "Playlist exists", Toast.LENGTH_SHORT)
.show()
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.removeItem(R.id.action_grid_size)
menu.removeItem(R.id.action_layout_type)
menu.removeItem(R.id.action_sort_order)
super.onCreateOptionsMenu(menu, inflater)
}
}

View File

@ -5,18 +5,17 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.lifecycle.Observer
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.extensions.dipToPix import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.util.PlaylistsUtil
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
@ -32,7 +31,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
parametersOf(arguments.extraPlaylist) parametersOf(arguments.extraPlaylist)
} }
private lateinit var playlist: Playlist private lateinit var playlist: PlaylistWithSongs
private lateinit var adapter: SongAdapter private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null private var wrappedAdapter: RecyclerView.Adapter<*>? = null
@ -46,28 +45,23 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
mainActivity.hideBottomBarVisibility(false) mainActivity.hideBottomBarVisibility(false)
playlist = arguments.extraPlaylist playlist = arguments.extraPlaylist
toolbar.title = playlist.playlistEntity.playlistName
setUpRecyclerView() setUpRecyclerView()
viewModel.getSongs().observe(viewLifecycleOwner, Observer { viewModel.getSongs().observe(viewLifecycleOwner, {
songs(it) songs(it.toSongs())
})
viewModel.getPlaylist().observe(viewLifecycleOwner, Observer {
playlist = it
toolbar.title = it.name
}) })
} }
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
if (playlist is AbsCustomPlaylist) { recyclerViewDragDropManager = RecyclerViewDragDropManager()
adapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null) val animator = RefactoredDefaultItemAnimator()
recyclerView.adapter = adapter adapter =
} else { OrderablePlaylistSongAdapter(
recyclerViewDragDropManager = RecyclerViewDragDropManager() playlist.playlistEntity,
val animator = RefactoredDefaultItemAnimator() requireActivity(),
adapter = OrderablePlaylistSongAdapter(requireActivity(),
ArrayList(), ArrayList(),
R.layout.item_list, R.layout.item_list,
null, null,
@ -75,7 +69,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onMoveItem(fromPosition: Int, toPosition: Int) { override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem( if (PlaylistsUtil.moveItem(
requireContext(), requireContext(),
playlist.id, playlist.playlistEntity.playListId,
fromPosition, fromPosition,
toPosition toPosition
) )
@ -86,13 +80,13 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
} }
} }
}) })
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter) wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter)
recyclerView.adapter = wrappedAdapter recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator recyclerView.itemAnimator = animator
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
super.onChanged() super.onChanged()
@ -103,9 +97,9 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
val menuRes = if (playlist is AbsCustomPlaylist) val menuRes =/* if (playlist is AbsCustomPlaylist)
R.menu.menu_smart_playlist_detail R.menu.menu_smart_playlist_detail
else R.menu.menu_playlist_detail else*/ R.menu.menu_playlist_detail
inflater.inflate(menuRes, menu) inflater.inflate(menuRes, menu)
} }
@ -129,7 +123,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
return String(Character.toChars(unicode)) return String(Character.toChars(unicode))
} }
public override fun onPause() { override fun onPause() {
if (recyclerViewDragDropManager != null) { if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag() recyclerViewDragDropManager!!.cancelDrag()
} }
@ -160,11 +154,11 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
} }
fun songs(songs: List<Song>) { fun songs(songs: List<Song>) {
progressIndicator.hide()
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
adapter.swapDataSet(songs) adapter.swapDataSet(songs)
} else { } else {
showEmptyView() showEmptyView()
} }
} }
} }

View File

@ -3,42 +3,29 @@ package code.name.monkey.retromusic.fragments.playlists
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.PlaylistsUtil
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistDetailsViewModel( class PlaylistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private var playlist: Playlist private var playlist: PlaylistWithSongs
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val _playListSongs = MutableLiveData<List<Song>>() private val _playListSongs = MutableLiveData<List<Song>>()
private val _playlist = MutableLiveData<Playlist>().apply { private val _playlist = MutableLiveData<PlaylistWithSongs>().apply {
postValue(playlist) postValue(playlist)
} }
fun getPlaylist(): LiveData<Playlist> = _playlist fun getPlaylist(): LiveData<PlaylistWithSongs> = _playlist
fun getSongs(): LiveData<List<Song>> = _playListSongs fun getSongs(): LiveData<List<SongEntity>> = realRepository.playlistSongs(playlist.playlistEntity)
init {
loadPlaylistSongs(playlist)
}
private fun loadPlaylistSongs(playlist: Playlist) = viewModelScope.launch {
val songs = realRepository.getPlaylistSongs(playlist)
withContext(Main) { _playListSongs.postValue(songs) }
}
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
if (playlist !is AbsCustomPlaylist) { /*if (playlist !is AbsCustomPlaylist) {
// Playlist deleted // Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) { if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) {
//TODO Finish the page //TODO Finish the page
@ -54,7 +41,7 @@ class PlaylistDetailsViewModel(
} }
} }
} }
loadPlaylistSongs(playlist) loadPlaylistSongs(playlist)*/
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}
@ -64,4 +51,4 @@ class PlaylistDetailsViewModel(
override fun onPlayStateChanged() {} override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {} override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {} override fun onShuffleModeChanged() {}
} }

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@ class MainSettingsFragment : Fragment(), View.OnClickListener {
aboutSettings.setOnClickListener(this) aboutSettings.setOnClickListener(this)
buyProContainer.apply { buyProContainer.apply {
if (!App.isProVersion()) show() else hide() if (App.isProVersion()) hide() else show()
setOnClickListener { setOnClickListener {
NavigationUtil.goToProVersion(requireContext()) NavigationUtil.goToProVersion(requireContext())
} }

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