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

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.
### FAQ
### FAQ
Please read the FAQ here: https://del.dog/RetroFaq
In any case, you find or notice any bugs please report them by

View File

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

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
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.AsyncTask
import android.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
@ -18,10 +17,8 @@ import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha
@ -62,14 +59,11 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
playerQueueSheet.setPadding(
playerQueueSheet.paddingLeft,
(slideOffset * status_bar.height).toInt(),
@ -79,18 +73,17 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) {
BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false)
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
}
BottomSheetBehavior.STATE_COLLAPSED -> {
resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true)
mainActivity.getBottomSheetBehavior().setAllowDragging(true)
}
else -> {
activity.getBottomSheetBehavior().setAllowDragging(true)
mainActivity.getBottomSheetBehavior().setAllowDragging(true)
}
}
}
@ -139,8 +132,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun setupSheet() {
getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList)
playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
mainActivity.getBottomSheetBehavior().setAllowDragging(false)
getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false
}
@ -159,7 +151,6 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
recyclerViewDragDropManager?.cancelDrag()
super.onPause()
progressViewUpdateHelper.stop()
}
override fun playerToolbar(): Toolbar? {
@ -224,9 +215,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun toggleFavorite(song: Song) {
super.toggleFavorite(song)
MusicUtil.toggleFavorite(requireContext(), song)
if (song.id == MusicPlayerRemote.currentSong.id) {
updateFavorite()
updateIsFavorite()
}
}
@ -274,6 +264,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
override fun onQueueChanged() {
super.onQueueChanged()
updateLabel()
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue)
}
private fun updateSong() {
@ -372,7 +363,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
} else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
nextSong.apply {
text = "Next: $title"
text = title
show()
}
}
@ -478,36 +469,4 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
@SuppressLint("StaticFieldLeak")
private fun updateFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask?.cancel(false)
}
updateIsFavoriteTask =
object : AsyncTask<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.MenuItem
import android.view.View
import androidx.lifecycle.Observer
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.extensions.dipToPix
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
@ -32,7 +31,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
parametersOf(arguments.extraPlaylist)
}
private lateinit var playlist: Playlist
private lateinit var playlist: PlaylistWithSongs
private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
@ -46,28 +45,23 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
mainActivity.hideBottomBarVisibility(false)
playlist = arguments.extraPlaylist
toolbar.title = playlist.playlistEntity.playlistName
setUpRecyclerView()
viewModel.getSongs().observe(viewLifecycleOwner, Observer {
songs(it)
})
viewModel.getPlaylist().observe(viewLifecycleOwner, Observer {
playlist = it
toolbar.title = it.name
viewModel.getSongs().observe(viewLifecycleOwner, {
songs(it.toSongs())
})
}
private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(requireContext())
if (playlist is AbsCustomPlaylist) {
adapter = SongAdapter(requireActivity(), ArrayList(), R.layout.item_list, null)
recyclerView.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter(requireActivity(),
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter =
OrderablePlaylistSongAdapter(
playlist.playlistEntity,
requireActivity(),
ArrayList(),
R.layout.item_list,
null,
@ -75,7 +69,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem(
requireContext(),
playlist.id,
playlist.playlistEntity.playListId,
fromPosition,
toPosition
)
@ -86,13 +80,13 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
}
})
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter)
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter)
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
@ -103,9 +97,9 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val menuRes = if (playlist is AbsCustomPlaylist)
val menuRes =/* if (playlist is AbsCustomPlaylist)
R.menu.menu_smart_playlist_detail
else R.menu.menu_playlist_detail
else*/ R.menu.menu_playlist_detail
inflater.inflate(menuRes, menu)
}
@ -129,7 +123,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
return String(Character.toChars(unicode))
}
public override fun onPause() {
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
@ -160,11 +154,11 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
fun songs(songs: List<Song>) {
progressIndicator.hide()
if (songs.isNotEmpty()) {
adapter.swapDataSet(songs)
} else {
showEmptyView()
}
}
}

View File

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

View File

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

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

View File

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

View File

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

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