MVP is Gone

main
Hemanth S 2020-07-21 00:35:48 +05:30
parent f0663c2b43
commit 17a66450d4
30 changed files with 341 additions and 582 deletions

View File

@ -163,4 +163,9 @@ dependencies {
implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'me.jorgecastillo:androidcolorx:0.2.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4' debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'com.github.dhaval2404:imagepicker:1.7.1'
def koin_version = "2.1.5"
implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-viewmodel:$koin_version"
} }

View File

@ -107,7 +107,7 @@
</activity> </activity>
<activity android:name=".activities.albums.AlbumDetailsActivity" /> <activity android:name=".activities.albums.AlbumDetailsActivity" />
<activity android:name=".activities.artists.ArtistDetailActivity" /> <activity android:name=".activities.artists.ArtistDetailActivity" />
<activity android:name=".activities.PlaylistDetailActivity" /> <activity android:name=".activities.playlist.PlaylistDetailActivity" />
<activity android:name=".activities.PlayingQueueActivity" /> <activity android:name=".activities.PlayingQueueActivity" />
<activity android:name=".activities.AboutActivity" /> <activity android:name=".activities.AboutActivity" />
<activity android:name=".activities.tageditor.AlbumTagEditorActivity" /> <activity android:name=".activities.tageditor.AlbumTagEditorActivity" />
@ -116,7 +116,7 @@
<activity android:name=".activities.LyricsActivity" /> <activity android:name=".activities.LyricsActivity" />
<activity android:name=".activities.UserInfoActivity" /> <activity android:name=".activities.UserInfoActivity" />
<activity android:name=".activities.SupportDevelopmentActivity" /> <activity android:name=".activities.SupportDevelopmentActivity" />
<activity android:name=".activities.GenreDetailsActivity" /> <activity android:name=".activities.genre.GenreDetailsActivity" />
<activity android:name=".activities.LicenseActivity" /> <activity android:name=".activities.LicenseActivity" />
<activity android:name=".activities.PurchaseActivity" /> <activity android:name=".activities.PurchaseActivity" />
<activity android:name=".activities.WhatsNewActivity" /> <activity android:name=".activities.WhatsNewActivity" />
@ -124,7 +124,7 @@
<activity android:name=".activities.ShareInstagramStory" /> <activity android:name=".activities.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" /> <activity android:name=".activities.DriveModeActivity" />
<activity <activity
android:name=".activities.SearchActivity" android:name=".activities.search.SearchActivity"
android:windowSoftInputMode="stateVisible" /> android:windowSoftInputMode="stateVisible" />
<activity <activity

View File

@ -20,8 +20,11 @@ import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.providers.provideModules
import com.anjlab.android.iab.v3.BillingProcessor import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails import com.anjlab.android.iab.v3.TransactionDetails
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class App : MultiDexApplication() { class App : MultiDexApplication() {
@ -31,6 +34,10 @@ class App : MultiDexApplication() {
super.onCreate() super.onCreate()
instance = this instance = this
startKoin {
androidContext(this@App)
modules(listOf(mainModule, provideModules))
}
// default theme // default theme
if (!ThemeStore.isConfigured(this, 3)) { if (!ThemeStore.isConfigured(this, 3)) {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)

View File

@ -0,0 +1,40 @@
package code.name.monkey.retromusic
import code.name.monkey.retromusic.activities.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.activities.artists.ArtistDetailsViewModel
import code.name.monkey.retromusic.activities.genre.GenreDetailsViewModel
import code.name.monkey.retromusic.activities.playlist.PlaylistDetailsViewModel
import code.name.monkey.retromusic.activities.search.SearchViewModel
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Playlist
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module
val mainModule = module {
viewModel {
LibraryViewModel(get())
}
viewModel { (albumId: Int) ->
AlbumDetailsViewModel(get(), albumId)
}
viewModel { (artistId: Int) ->
ArtistDetailsViewModel(get(), artistId)
}
viewModel { (playlist: Playlist) ->
PlaylistDetailsViewModel(get(), playlist)
}
viewModel { (genre: Genre) ->
GenreDetailsViewModel(get(), genre)
}
viewModel {
SearchViewModel(get())
}
}

View File

@ -1,26 +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
/**
* Created by hemanths on 2019-10-23.
*/
sealed class Result<out T : Any> {
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable) : Result<Nothing>()
}

View File

@ -13,7 +13,6 @@ import android.view.View
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
@ -64,6 +63,7 @@ import com.google.android.play.core.install.model.InstallStatus.INSTALLED
import com.google.android.play.core.install.model.UpdateAvailability import com.google.android.play.core.install.model.UpdateAvailability
import com.google.android.play.core.tasks.Task import com.google.android.play.core.tasks.Task
import kotlinx.android.synthetic.main.activity_main_content.* import kotlinx.android.synthetic.main.activity_main_content.*
import org.koin.android.ext.android.inject
import java.util.* import java.util.*
class MainActivity : AbsSlidingMusicPanelActivity(), class MainActivity : AbsSlidingMusicPanelActivity(),
@ -74,7 +74,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(),
const val APP_UPDATE_REQUEST_CODE = 9002 const val APP_UPDATE_REQUEST_CODE = 9002
} }
lateinit var libraryViewModel: LibraryViewModel val libraryViewModel: LibraryViewModel by inject()
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
private val intentFilter = IntentFilter(Intent.ACTION_SCREEN_OFF) private val intentFilter = IntentFilter(Intent.ACTION_SCREEN_OFF)
private lateinit var currentFragment: MainActivityFragmentCallbacks private lateinit var currentFragment: MainActivityFragmentCallbacks
@ -123,9 +123,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(),
hideStatusBar() hideStatusBar()
setBottomBarVisibility(View.VISIBLE) setBottomBarVisibility(View.VISIBLE)
libraryViewModel = ViewModelProvider(this).get(LibraryViewModel::class.java)
addMusicServiceEventListener(libraryViewModel) addMusicServiceEventListener(libraryViewModel)
if (savedInstanceState == null) { if (savedInstanceState == null) {
selectedFragment(PreferenceUtil.lastPage) selectedFragment(PreferenceUtil.lastPage)
} else { } else {

View File

@ -10,7 +10,6 @@ import android.view.SubMenu
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -37,7 +36,6 @@ import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder
import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsView
import code.name.monkey.retromusic.rest.model.LastFmAlbum import code.name.monkey.retromusic.rest.model.LastFmAlbum
import code.name.monkey.retromusic.util.* import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
@ -45,10 +43,12 @@ import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_album.* import kotlinx.android.synthetic.main.activity_album.*
import kotlinx.android.synthetic.main.activity_album_content.* import kotlinx.android.synthetic.main.activity_album_content.*
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
import android.util.Pair as UtilPair import android.util.Pair as UtilPair
class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, CabHolder { class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let { cab?.let {
if (it.isActive) it.finish() if (it.isActive) it.finish()
@ -68,7 +68,9 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
return cab as MaterialCab return cab as MaterialCab
} }
private lateinit var viewModel: AlbumDetailsViewModel private val detailsViewModel: AlbumDetailsViewModel by viewModel {
parametersOf(extraNotNull<Int>(EXTRA_ALBUM_ID).value)
}
private lateinit var simpleSongAdapter: SimpleSongAdapter private lateinit var simpleSongAdapter: SimpleSongAdapter
private lateinit var album: Album private lateinit var album: Album
private lateinit var artistImage: ImageView private lateinit var artistImage: ImageView
@ -100,19 +102,19 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
windowEnterTransition() windowEnterTransition()
val albumId = extraNotNull<Int>(EXTRA_ALBUM_ID).value addMusicServiceEventListener(detailsViewModel)
ActivityCompat.postponeEnterTransition(this) ActivityCompat.postponeEnterTransition(this)
val viewModelFactory = AlbumDetailsViewModelFactory(application, albumId) //val viewModelFactory = AlbumDetailsViewModelFactory(application, albumId)
viewModel = ViewModelProvider(this, viewModelFactory).get(AlbumDetailsViewModel::class.java) //viewModel = ViewModelProvider(this, viewModelFactory).get(AlbumDetailsViewModel::class.java)
addMusicServiceEventListener(viewModel)
viewModel.getAlbum().observe(this, androidx.lifecycle.Observer { detailsViewModel.getAlbum().observe(this, androidx.lifecycle.Observer {
ActivityCompat.startPostponedEnterTransition(this@AlbumDetailsActivity) ActivityCompat.startPostponedEnterTransition(this@AlbumDetailsActivity)
album(it) album(it)
}) })
viewModel.getArtist().observe(this, androidx.lifecycle.Observer { detailsViewModel.getArtist().observe(this, androidx.lifecycle.Observer {
loadArtistImage(it) loadArtistImage(it)
}) })
viewModel.getAlbumInfo().observe(this, androidx.lifecycle.Observer { detailsViewModel.getAlbumInfo().observe(this, androidx.lifecycle.Observer {
aboutAlbum(it) aboutAlbum(it)
}) })
setupRecyclerView() setupRecyclerView()
@ -153,11 +155,11 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
} }
} }
override fun complete() { fun complete() {
ActivityCompat.startPostponedEnterTransition(this) ActivityCompat.startPostponedEnterTransition(this)
} }
override fun album(album: Album) { fun album(album: Album) {
complete() complete()
if (album.songs!!.isEmpty()) { if (album.songs!!.isEmpty()) {
finish() finish()
@ -190,11 +192,11 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
} }
loadAlbumCover() loadAlbumCover()
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
viewModel.loadArtist(album.artistId) detailsViewModel.loadArtist(album.artistId)
viewModel.loadAlbumInfo(album) detailsViewModel.loadAlbumInfo(album)
} }
override fun moreAlbums(albums: List<Album>) { fun moreAlbums(albums: List<Album>) {
moreTitle.show() moreTitle.show()
moreRecyclerView.show() moreRecyclerView.show()
moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName) moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName)
@ -209,7 +211,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
moreRecyclerView.adapter = albumAdapter moreRecyclerView.adapter = albumAdapter
} }
override fun aboutAlbum(lastFmAlbum: LastFmAlbum) { fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) { if (lastFmAlbum.album != null) {
if (lastFmAlbum.album.wiki != null) { if (lastFmAlbum.album.wiki != null) {
aboutAlbumText.show() aboutAlbumText.show()
@ -230,7 +232,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
} }
} }
override fun loadArtistImage(artist: Artist) { fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(this), artist) ArtistGlideRequest.Builder.from(Glide.with(this), artist)
.generatePalette(this) .generatePalette(this)
.build() .build()
@ -391,8 +393,9 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, C
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
removeMusicServiceEventListener(viewModel) removeMusicServiceEventListener(detailsViewModel)
} }
companion object { companion object {
const val EXTRA_ALBUM_ID = "extra_album_id" const val EXTRA_ALBUM_ID = "extra_album_id"

View File

@ -1,9 +1,8 @@
package code.name.monkey.retromusic.activities.albums package code.name.monkey.retromusic.activities.albums
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
@ -16,10 +15,10 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AlbumDetailsViewModel( class AlbumDetailsViewModel(
application: Application, private val repository: RepositoryImpl,
private val albumId: Int private val albumId: Int
) : AndroidViewModel(application), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val _repository = RepositoryImpl(application.applicationContext)
private val _album = MutableLiveData<Album>() private val _album = MutableLiveData<Album>()
private val _artist = MutableLiveData<Artist>() private val _artist = MutableLiveData<Artist>()
private val _lastFmAlbum = MutableLiveData<LastFmAlbum>() private val _lastFmAlbum = MutableLiveData<LastFmAlbum>()
@ -38,18 +37,18 @@ class AlbumDetailsViewModel(
} }
fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) { fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) {
val lastFmAlbum = _repository.albumInfo(album.artistName ?: "-", album.title ?: "-") val lastFmAlbum = repository.albumInfo(album.artistName ?: "-", album.title ?: "-")
_lastFmAlbum.postValue(lastFmAlbum) _lastFmAlbum.postValue(lastFmAlbum)
} }
fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) { fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) {
val artist = _repository.artistById(artistId) val artist = repository.artistById(artistId)
_artist.postValue(artist) _artist.postValue(artist)
} }
private val loadAlbumAsync: Deferred<Album?> private val loadAlbumAsync: Deferred<Album?>
get() = viewModelScope.async(Dispatchers.IO) { get() = viewModelScope.async(Dispatchers.IO) {
_repository.albumById(albumId) repository.albumById(albumId)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {

View File

@ -1,19 +0,0 @@
package code.name.monkey.retromusic.activities.albums
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class AlbumDetailsViewModelFactory(
private val application: Application,
private val albumId: Int
) :
ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(AlbumDetailsViewModel::class.java)) {
AlbumDetailsViewModel(application, albumId) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}

View File

@ -11,7 +11,6 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -31,7 +30,6 @@ import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsView
import code.name.monkey.retromusic.rest.model.LastFmArtist import code.name.monkey.retromusic.rest.model.LastFmArtist
import code.name.monkey.retromusic.util.* import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
@ -39,10 +37,12 @@ import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_artist_content.* import kotlinx.android.synthetic.main.activity_artist_content.*
import kotlinx.android.synthetic.main.activity_artist_details.* import kotlinx.android.synthetic.main.activity_artist_details.*
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView, CabHolder { class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab { override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let { cab?.let {
if (it.isActive) it.finish() if (it.isActive) it.finish()
@ -68,7 +68,9 @@ class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView,
private lateinit var songAdapter: SimpleSongAdapter private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false private var forceDownload: Boolean = false
private lateinit var viewModel: ArtistDetailsViewModel private val detailsViewModel: ArtistDetailsViewModel by viewModel {
parametersOf(extraNotNull<Int>(EXTRA_ARTIST_ID).value)
}
override fun createContentView(): View { override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_artist_details) return wrapSlidingMusicPanel(R.layout.activity_artist_details)
@ -93,23 +95,16 @@ class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView,
setBottomBarVisibility(View.GONE) setBottomBarVisibility(View.GONE)
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
windowEnterTransition() windowEnterTransition()
ActivityCompat.postponeEnterTransition(this)
val artistId = extraNotNull<Int>(EXTRA_ARTIST_ID).value addMusicServiceEventListener(detailsViewModel)
val viewModelFactory = ArtistDetailsViewModelFactory(application, artistId) detailsViewModel.getArtist().observe(this, androidx.lifecycle.Observer {
viewModel =
ViewModelProvider(this, viewModelFactory).get(ArtistDetailsViewModel::class.java)
addMusicServiceEventListener(viewModel)
viewModel.getArtist().observe(this, androidx.lifecycle.Observer {
ActivityCompat.startPostponedEnterTransition(this@ArtistDetailActivity) ActivityCompat.startPostponedEnterTransition(this@ArtistDetailActivity)
artist(it) artist(it)
}) })
viewModel.getArtistInfo().observe(this, androidx.lifecycle.Observer { detailsViewModel.getArtistInfo().observe(this, androidx.lifecycle.Observer {
artistInfo(it) artistInfo(it)
}) })
ActivityCompat.postponeEnterTransition(this)
setupRecyclerView() setupRecyclerView()
playAction.apply { playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
} }
@ -155,14 +150,11 @@ class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView,
} }
} }
override fun showEmptyView() { fun complete() {
}
override fun complete() {
ActivityCompat.startPostponedEnterTransition(this) ActivityCompat.startPostponedEnterTransition(this)
} }
override fun artist(artist: Artist) { fun artist(artist: Artist) {
complete() complete()
if (artist.songCount <= 0) { if (artist.songCount <= 0) {
finish() finish()
@ -203,10 +195,10 @@ class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView,
) { ) {
biography = null biography = null
this.lang = lang this.lang = lang
viewModel.loadBiography(name, lang, null) detailsViewModel.loadBiography(name, lang, null)
} }
override fun artistInfo(lastFmArtist: LastFmArtist?) { fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null) { if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) { if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
@ -323,7 +315,7 @@ class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView,
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
removeMusicServiceEventListener(viewModel) removeMusicServiceEventListener(detailsViewModel)
} }
companion object { companion object {

View File

@ -1,9 +1,8 @@
package code.name.monkey.retromusic.activities.artists package code.name.monkey.retromusic.activities.artists
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
@ -15,15 +14,15 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
application: Application, private val repository: RepositoryImpl,
private val artistId: Int private val artistId: Int
) : AndroidViewModel(application), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?> private val loadArtistDetailsAsync: Deferred<Artist?>
get() = viewModelScope.async(Dispatchers.IO) { get() = viewModelScope.async(Dispatchers.IO) {
_repository.artistById(artistId) repository.artistById(artistId)
} }
private val _repository = RepositoryImpl(application.applicationContext)
private val _artist = MutableLiveData<Artist>() private val _artist = MutableLiveData<Artist>()
private val _lastFmArtist = MutableLiveData<LastFmArtist>() private val _lastFmArtist = MutableLiveData<LastFmArtist>()
@ -41,7 +40,7 @@ class ArtistDetailsViewModel(
} }
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch { fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch {
val info = _repository.artistInfo(name, lang, cache) val info = repository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info) _lastFmArtist.postValue(info)
} }

View File

@ -1,19 +0,0 @@
package code.name.monkey.retromusic.activities.artists
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ArtistDetailsViewModelFactory(
private val application: Application,
private val artistId: Int
) :
ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(ArtistDetailsViewModel::class.java)) {
ArtistDetailsViewModel(application, artistId) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}

View File

@ -1,4 +1,4 @@
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities.genre
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
@ -17,24 +17,25 @@ import code.name.monkey.retromusic.helper.menu.GenreMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter.GenreDetailsPresenterImpl
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsView
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab import com.afollestad.materialcab.MaterialCab
import kotlinx.android.synthetic.main.activity_playlist_detail.* import kotlinx.android.synthetic.main.activity_playlist_detail.*
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
/** /**
* @author Hemanth S (h4h13). * @author Hemanth S (h4h13).
*/ */
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView { class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder {
private lateinit var genreDetailsPresenter: GenreDetailsPresenter private val detailsViewModel: GenreDetailsViewModel by viewModel {
parametersOf(extraNotNull<Genre>(EXTRA_GENRE_ID).value)
}
private lateinit var genre: Genre private lateinit var genre: Genre
private lateinit var songAdapter: ShuffleButtonSongAdapter private lateinit var songAdapter: ShuffleButtonSongAdapter
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
@ -62,38 +63,25 @@ class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDet
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
setBottomBarVisibility(View.GONE) setBottomBarVisibility(View.GONE)
applyToolbar(toolbar)
genre = extraNotNull<Genre>(EXTRA_GENRE_ID).value
setUpToolBar()
setupRecyclerView() setupRecyclerView()
genreDetailsPresenter = detailsViewModel.getSongs().observe(this, androidx.lifecycle.Observer {
GenreDetailsPresenterImpl(RepositoryImpl(this)) songs(it)
genreDetailsPresenter.attachView(this) })
}
private fun setUpToolBar() { detailsViewModel.getGenre().observe(this, androidx.lifecycle.Observer {
applyToolbar(toolbar) genre = it
title = genre.name supportActionBar?.title = it.name
} })
override fun onResume() { addMusicServiceEventListener(detailsViewModel)
super.onResume()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
override fun onDestroy() {
super.onDestroy()
genreDetailsPresenter.detachView()
} }
override fun createContentView(): View { override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail) return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
} }
override fun showEmptyView() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_genre_detail, menu) menuInflater.inflate(R.menu.menu_genre_detail, menu)
@ -122,7 +110,7 @@ class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDet
}) })
} }
override fun songs(songs: List<Song>) { fun songs(songs: List<Song>) {
songAdapter.swapDataSet(songs) songAdapter.swapDataSet(songs)
} }
@ -149,11 +137,6 @@ class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDet
} }
} }
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
companion object { companion object {
const val EXTRA_GENRE_ID = "extra_genre_id" const val EXTRA_GENRE_ID = "extra_genre_id"
} }

View File

@ -0,0 +1,49 @@
package code.name.monkey.retromusic.activities.genre
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.RepositoryImpl
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class GenreDetailsViewModel(
private val repository: RepositoryImpl,
private val genre: Genre
) : ViewModel(), MusicServiceEventListener {
private val _playListSongs = MutableLiveData<List<Song>>()
private val _genre = MutableLiveData<Genre>().apply {
postValue(genre)
}
fun getSongs(): LiveData<List<Song>> = _playListSongs
fun getGenre(): LiveData<Genre> = _genre
init {
loadGenreSongs(genre)
}
private fun loadGenreSongs(genre: Genre) = viewModelScope.launch {
val songs = repository.getGenre(genre.id)
withContext(Main) { _playListSongs.postValue(songs) }
}
override fun onMediaStoreChanged() {
loadGenreSongs(genre)
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View File

@ -1,9 +1,10 @@
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities.playlist
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
@ -16,14 +17,9 @@ import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.AbsCustomPlaylist import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter.PlaylistSongsPresenterImpl
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsView
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.util.PlaylistsUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
@ -32,11 +28,15 @@ import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemA
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playlist_detail.* import kotlinx.android.synthetic.main.activity_playlist_detail.*
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView { class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder {
private lateinit var presenter: PlaylistSongsPresenter private val viewModel: PlaylistDetailsViewModel by viewModel {
parametersOf(extraNotNull<Playlist>(EXTRA_PLAYLIST).value)
}
private lateinit var playlist: Playlist private lateinit var playlist: Playlist
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
private lateinit var adapter: SongAdapter private lateinit var adapter: SongAdapter
@ -52,13 +52,20 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
setLightNavigationBar(true) setLightNavigationBar(true)
setBottomBarVisibility(View.GONE) setBottomBarVisibility(View.GONE)
presenter = PlaylistSongsPresenterImpl(RepositoryImpl(this))
presenter.attachView(this)
playlist = extraNotNull<Playlist>(EXTRA_PLAYLIST).value playlist = extraNotNull<Playlist>(EXTRA_PLAYLIST).value
setUpToolBar() setUpToolBar()
setUpRecyclerView() setUpRecyclerView()
viewModel.getSongs().observe(this, Observer {
songs(it)
})
viewModel.getPlaylist().observe(this, Observer {
playlist = it
supportActionBar?.title = it.name
})
addMusicServiceEventListener(viewModel)
} }
override fun createContentView(): View { override fun createContentView(): View {
@ -66,7 +73,6 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
} }
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.layoutManager = LinearLayoutManager(this)
if (playlist is AbsCustomPlaylist) { if (playlist is AbsCustomPlaylist) {
adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, this) adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, this)
@ -108,11 +114,6 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
}) })
} }
override fun onResume() {
super.onResume()
presenter.loadPlaylistSongs(playlist)
}
private fun setUpToolBar() { private fun setUpToolBar() {
applyToolbar(toolbar) applyToolbar(toolbar)
title = playlist.name title = playlist.name
@ -162,28 +163,6 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
} }
} }
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
if (playlist !is AbsCustomPlaylist) {
// Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(this, playlist.id)) {
finish()
return
}
// Playlist renamed
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist.id.toLong())
if (playlistName != playlist.name) {
playlist = PlaylistLoader.getPlaylist(this, playlist.id)
setToolbarTitle(playlist.name)
}
}
presenter.loadPlaylistSongs(playlist)
}
private fun setToolbarTitle(title: String) {
supportActionBar?.title = title
}
private fun checkForPadding() { private fun checkForPadding() {
val height = DensityUtil.dip2px(this, 52f) val height = DensityUtil.dip2px(this, 52f)
recyclerView.setPadding(0, 0, 0, (height)) recyclerView.setPadding(0, 0, 0, (height))
@ -223,16 +202,19 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
wrappedAdapter = null wrappedAdapter = null
} }
super.onDestroy() super.onDestroy()
presenter.detachView()
} }
override fun showEmptyView() { fun showEmptyView() {
empty.visibility = View.VISIBLE empty.visibility = View.VISIBLE
emptyText.visibility = View.VISIBLE emptyText.visibility = View.VISIBLE
} }
override fun songs(songs: List<Song>) { fun songs(songs: List<Song>) {
if (songs.isNotEmpty()) {
adapter.swapDataSet(songs) adapter.swapDataSet(songs)
} else {
showEmptyView()
}
} }
companion object { companion object {

View File

@ -0,0 +1,67 @@
package code.name.monkey.retromusic.activities.playlist
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.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.loaders.PlaylistLoader
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.providers.RepositoryImpl
import code.name.monkey.retromusic.util.PlaylistsUtil
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistDetailsViewModel(
private val repository: RepositoryImpl,
private var playlist: Playlist
) : ViewModel(), MusicServiceEventListener {
private val _playListSongs = MutableLiveData<List<Song>>()
private val _playlist = MutableLiveData<Playlist>().apply {
postValue(playlist)
}
fun getPlaylist(): LiveData<Playlist> = _playlist
fun getSongs(): LiveData<List<Song>> = _playListSongs
init {
loadPlaylistSongs(playlist)
}
private fun loadPlaylistSongs(playlist: Playlist) = viewModelScope.launch {
val songs = repository.getPlaylistSongs(playlist)
withContext(Main) { _playListSongs.postValue(songs) }
}
override fun onMediaStoreChanged() {
if (playlist !is AbsCustomPlaylist) {
// Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) {
//TODO Finish the page
return
}
// Playlist renamed
val playlistName =
PlaylistsUtil.getNameForPlaylist(App.getContext(), playlist.id.toLong())
if (playlistName != playlist.name) {
playlist = PlaylistLoader.getPlaylist(App.getContext(), playlist.id)
_playlist.postValue(playlist)
}
}
loadPlaylistSongs(playlist)
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View File

@ -1,4 +1,4 @@
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities.search
import android.app.Activity import android.app.Activity
import android.app.Service import android.app.Service
@ -24,19 +24,17 @@ import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.SearchAdapter import code.name.monkey.retromusic.adapter.SearchAdapter
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter import code.name.monkey.retromusic.extensions.extra
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter.SearchPresenterImpl
import code.name.monkey.retromusic.mvp.presenter.SearchView
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import kotlinx.android.synthetic.main.activity_search.* import kotlinx.android.synthetic.main.activity_search.*
import org.koin.android.ext.android.inject
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView { class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher {
private lateinit var presenter: SearchPresenter private val viewModel: SearchViewModel by inject()
private var searchAdapter: SearchAdapter? = null private var searchAdapter: SearchAdapter? = null
private var query: String? = null private var query: String? = null
@ -49,14 +47,11 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
presenter = SearchPresenterImpl(RepositoryImpl(this))
presenter.attachView(this)
setupRecyclerView() setupRecyclerView()
setUpToolBar() setUpToolBar()
setupSearchView() setupSearchView()
if (intent.getBooleanExtra(EXTRA_SHOW_MIC, false)) { if (extra<Boolean>(EXTRA_SHOW_MIC).value == true) {
startMicSearch() startMicSearch()
} }
@ -84,6 +79,10 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
if (savedInstanceState != null) { if (savedInstanceState != null) {
query = savedInstanceState.getString(QUERY) query = savedInstanceState.getString(QUERY)
} }
viewModel.getSearchResult().observe(this, androidx.lifecycle.Observer {
showData(it)
})
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
@ -114,11 +113,6 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
searchView.addTextChangedListener(this) searchView.addTextChangedListener(this)
} }
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putString(QUERY, query) outState.putString(QUERY, query)
@ -133,7 +127,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
TransitionManager.beginDelayedTransition(appBarLayout) TransitionManager.beginDelayedTransition(appBarLayout)
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
presenter.search(query) viewModel.search(query)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
@ -158,12 +152,16 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
} }
} }
override fun showEmptyView() { private fun showEmptyView() {
searchAdapter?.swapDataSet(ArrayList()) searchAdapter?.swapDataSet(ArrayList())
} }
override fun showData(data: MutableList<Any>) { private fun showData(data: MutableList<Any>) {
if (data.isNotEmpty()) {
searchAdapter?.swapDataSet(data) searchAdapter?.swapDataSet(data)
} else {
showEmptyView()
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -175,7 +173,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
query = result?.get(0) query = result?.get(0)
searchView.setText(query, BufferType.EDITABLE) searchView.setText(query, BufferType.EDITABLE)
presenter.search(query!!) viewModel.search(query!!)
} }
} }
} }
@ -190,7 +188,10 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()) intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt)) intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt))
try { try {
startActivityForResult(intent, REQ_CODE_SPEECH_INPUT) startActivityForResult(
intent,
REQ_CODE_SPEECH_INPUT
)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
e.printStackTrace() e.printStackTrace()
Toast.makeText(this, getString(R.string.speech_not_supported), Toast.LENGTH_SHORT) Toast.makeText(this, getString(R.string.speech_not_supported), Toast.LENGTH_SHORT)

View File

@ -0,0 +1,22 @@
package code.name.monkey.retromusic.activities.search
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.providers.RepositoryImpl
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SearchViewModel(private val repository: RepositoryImpl) : ViewModel() {
private val results = MutableLiveData<MutableList<Any>>()
fun getSearchResult(): LiveData<MutableList<Any>> = results
fun search(query: String?) = viewModelScope.launch(IO) {
val result = repository.search(query)
withContext(Main) { results.postValue(result) }
}
}

View File

@ -17,7 +17,7 @@ package code.name.monkey.retromusic.appshortcuts
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import code.name.monkey.retromusic.activities.SearchActivity import code.name.monkey.retromusic.activities.search.SearchActivity
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType

View File

@ -1,25 +1,23 @@
package code.name.monkey.retromusic.fragments package code.name.monkey.retromusic.fragments
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.adapter.HomeAdapter import code.name.monkey.retromusic.adapter.HomeAdapter
import code.name.monkey.retromusic.fragments.ReloadType.* import code.name.monkey.retromusic.fragments.ReloadType.*
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.providers.RepositoryImpl import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LibraryViewModel(application: Application) : class LibraryViewModel(
AndroidViewModel(application), MusicServiceEventListener { private val repository: RepositoryImpl
) : ViewModel(), MusicServiceEventListener {
private val _repository: Repository = RepositoryImpl(application.applicationContext)
private val _albums = MutableLiveData<List<Album>>() private val _albums = MutableLiveData<List<Album>>()
private val _songs = MutableLiveData<List<Song>>() private val _songs = MutableLiveData<List<Song>>()
private val _artists = MutableLiveData<List<Artist>>() private val _artists = MutableLiveData<List<Artist>>()
@ -52,12 +50,12 @@ class LibraryViewModel(application: Application) :
private fun loadHomeSections() = viewModelScope.launch { private fun loadHomeSections() = viewModelScope.launch {
val list = mutableListOf<Home>() val list = mutableListOf<Home>()
val result = listOf( val result = listOf(
_repository.topArtists(), repository.topArtists(),
_repository.topAlbums(), repository.topAlbums(),
_repository.recentArtists(), repository.recentArtists(),
_repository.recentAlbums(), repository.recentAlbums(),
_repository.suggestions(), repository.suggestions(),
_repository.favoritePlaylist() repository.favoritePlaylist()
) )
result.forEach { result.forEach {
if (it != null && it.arrayList.isNotEmpty()) { if (it != null && it.arrayList.isNotEmpty()) {
@ -75,27 +73,27 @@ class LibraryViewModel(application: Application) :
private val loadSongs: Deferred<List<Song>> private val loadSongs: Deferred<List<Song>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
_repository.allSongs() repository.allSongs()
} }
private val loadAlbums: Deferred<List<Album>> private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
_repository.allAlbums() repository.allAlbums()
} }
private val loadArtists: Deferred<List<Artist>> private val loadArtists: Deferred<List<Artist>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
_repository.allArtists() repository.allArtists()
} }
private val loadPlaylists: Deferred<List<Playlist>> private val loadPlaylists: Deferred<List<Playlist>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
_repository.allPlaylists() repository.allPlaylists()
} }
private val loadGenres: Deferred<List<Genre>> private val loadGenres: Deferred<List<Genre>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
_repository.allGenres() repository.allGenres()
} }
fun forceReload(reloadType: ReloadType) = viewModelScope.launch { fun forceReload(reloadType: ReloadType) = viewModelScope.launch {

View File

@ -1,24 +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.mvp
/**
* Created by hemanths on 09/08/17.
*/
interface BaseView {
fun showEmptyView()
}

View File

@ -1,30 +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.mvp
import androidx.annotation.CallSuper
/**
* Created by hemanths on 16/08/17.
*/
interface Presenter<T> {
@CallSuper
fun attachView(view: T)
@CallSuper
fun detachView()
}

View File

@ -1,30 +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.mvp;
/**
* Created by hemanths on 2019-09-04.
*/
public abstract class PresenterImpl<T> {
protected T view;
public void attachView(T view) {
this.view = view;
}
public void detachView() {
view = null;
}
}

View File

@ -1,35 +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.mvp.presenter
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.rest.model.LastFmAlbum
/**
* Created by hemanths on 20/08/17.
*/
interface AlbumDetailsView {
fun album(album: Album)
fun complete()
fun loadArtistImage(artist: Artist)
fun moreAlbums(albums: List<Album>)
fun aboutAlbum(lastFmAlbum: LastFmAlbum)
}

View File

@ -1,31 +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.mvp.presenter
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.BaseView
import code.name.monkey.retromusic.rest.model.LastFmArtist
/**
* Created by hemanths on 20/08/17.
*/
interface ArtistDetailsView : BaseView {
fun artist(artist: Artist)
fun artistInfo(lastFmArtist: LastFmArtist?)
fun complete()
}

View File

@ -1,59 +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.mvp.presenter
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.BaseView
import code.name.monkey.retromusic.mvp.Presenter
import code.name.monkey.retromusic.mvp.PresenterImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main
import kotlin.coroutines.CoroutineContext
/**
* Created by hemanths on 20/08/17.
*/
interface GenreDetailsView : BaseView {
fun songs(songs: List<Song>)
}
interface GenreDetailsPresenter : Presenter<GenreDetailsView> {
fun loadGenreSongs(genreId: Int)
class GenreDetailsPresenterImpl constructor(
private val repository: Repository
) : PresenterImpl<GenreDetailsView>(), GenreDetailsPresenter, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
override fun detachView() {
super.detachView()
job.cancel()
}
override fun loadGenreSongs(genreId: Int) {
launch {
val result = repository.getGenre(genreId)
withContext(Main) { view?.songs(result) }
}
}
}
}

View File

@ -1,63 +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.mvp.presenter
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.BaseView
import code.name.monkey.retromusic.mvp.Presenter
import code.name.monkey.retromusic.mvp.PresenterImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Created by hemanths on 20/08/17.
*/
interface PlaylistSongsView : BaseView {
fun songs(songs: List<Song>)
}
interface PlaylistSongsPresenter : Presenter<PlaylistSongsView> {
fun loadPlaylistSongs(playlist: Playlist)
class PlaylistSongsPresenterImpl constructor(
private val repository: Repository
) : PresenterImpl<PlaylistSongsView>(), PlaylistSongsPresenter, CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = IO + job
override fun loadPlaylistSongs(playlist: Playlist) {
launch {
val songs = repository.getPlaylistSongs(playlist)
withContext(Main) { view?.songs(songs) }
}
}
override fun detachView() {
super.detachView()
job.cancel()
}
}
}

View File

@ -1,61 +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.mvp.presenter
import code.name.monkey.retromusic.mvp.BaseView
import code.name.monkey.retromusic.mvp.Presenter
import code.name.monkey.retromusic.mvp.PresenterImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
/**
* Created by hemanths on 20/08/17.
*/
interface SearchView : BaseView {
fun showData(data: MutableList<Any>)
}
interface SearchPresenter : Presenter<SearchView> {
fun search(query: String?)
class SearchPresenterImpl constructor(
private val repository: Repository
) : PresenterImpl<SearchView>(), SearchPresenter, CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
private var job: Job = Job()
override fun detachView() {
super.detachView()
job.cancel()
}
override fun search(query: String?) {
launch {
val result = repository.search(query)
withContext(Dispatchers.Main) { view?.showData(result) }
}
}
}
}

View File

@ -0,0 +1,11 @@
package code.name.monkey.retromusic.providers
import org.eclipse.egit.github.core.Repository
import org.koin.dsl.bind
import org.koin.dsl.module
val provideModules = module {
single {
RepositoryImpl(get())
} bind Repository::class
}

View File

@ -30,13 +30,13 @@ import org.jetbrains.annotations.NotNull;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.AboutActivity; import code.name.monkey.retromusic.activities.AboutActivity;
import code.name.monkey.retromusic.activities.DriveModeActivity; import code.name.monkey.retromusic.activities.DriveModeActivity;
import code.name.monkey.retromusic.activities.GenreDetailsActivity; import code.name.monkey.retromusic.activities.genre.GenreDetailsActivity;
import code.name.monkey.retromusic.activities.LicenseActivity; import code.name.monkey.retromusic.activities.LicenseActivity;
import code.name.monkey.retromusic.activities.LyricsActivity; import code.name.monkey.retromusic.activities.LyricsActivity;
import code.name.monkey.retromusic.activities.PlayingQueueActivity; import code.name.monkey.retromusic.activities.PlayingQueueActivity;
import code.name.monkey.retromusic.activities.PlaylistDetailActivity; import code.name.monkey.retromusic.activities.playlist.PlaylistDetailActivity;
import code.name.monkey.retromusic.activities.PurchaseActivity; import code.name.monkey.retromusic.activities.PurchaseActivity;
import code.name.monkey.retromusic.activities.SearchActivity; import code.name.monkey.retromusic.activities.search.SearchActivity;
import code.name.monkey.retromusic.activities.SettingsActivity; import code.name.monkey.retromusic.activities.SettingsActivity;
import code.name.monkey.retromusic.activities.SupportDevelopmentActivity; import code.name.monkey.retromusic.activities.SupportDevelopmentActivity;
import code.name.monkey.retromusic.activities.UserInfoActivity; import code.name.monkey.retromusic.activities.UserInfoActivity;