More untested refactors.

This commit is contained in:
casey langen 2019-02-09 21:42:10 -08:00
parent dbab36feed
commit 04f5f2e645
5 changed files with 272 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import dagger.Component
import io.casey.musikcube.remote.ui.albums.activity.AlbumBrowseActivity
import io.casey.musikcube.remote.ui.category.activity.AllCategoriesActivity
import io.casey.musikcube.remote.ui.category.activity.CategoryBrowseActivity
import io.casey.musikcube.remote.ui.category.fragment.CategoryBrowseFragment
import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.ui.home.view.MainMetadataView
import io.casey.musikcube.remote.ui.playqueue.activity.PlayQueueActivity
@ -12,6 +13,7 @@ import io.casey.musikcube.remote.ui.settings.activity.RemoteEqActivity
import io.casey.musikcube.remote.ui.settings.activity.RemoteSettingsActivity
import io.casey.musikcube.remote.ui.settings.activity.SettingsActivity
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin
import io.casey.musikcube.remote.ui.shared.view.EmptyListView
@ -32,6 +34,9 @@ interface ViewComponent {
fun inject(activity: SettingsActivity)
fun inject(activity: TrackListActivity)
fun inject(fragment: BaseFragment)
fun inject(fragment: CategoryBrowseFragment)
fun inject(view: EmptyListView)
fun inject(view: MainMetadataView)

View File

@ -0,0 +1,219 @@
package io.casey.musikcube.remote.ui.category.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.ICategoryValue
import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.ui.albums.activity.AlbumBrowseActivity
import io.casey.musikcube.remote.ui.category.adapter.CategoryBrowseAdapter
import io.casey.musikcube.remote.ui.category.constant.Category
import io.casey.musikcube.remote.ui.category.constant.NavigationType
import io.casey.musikcube.remote.ui.shared.activity.Filterable
import io.casey.musikcube.remote.ui.shared.extension.EXTRA_ACTIVITY_TITLE
import io.casey.musikcube.remote.ui.shared.extension.initSearchMenu
import io.casey.musikcube.remote.ui.shared.extension.setFabVisible
import io.casey.musikcube.remote.ui.shared.extension.setupDefaultRecyclerView
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.view.EmptyListView
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
import io.casey.musikcube.remote.util.Debouncer
import io.reactivex.rxkotlin.subscribeBy
import java.lang.IllegalArgumentException
class CategoryBrowseFragment: BaseFragment(), Filterable {
private lateinit var adapter: CategoryBrowseAdapter
private var navigationType: NavigationType = NavigationType.Tracks
private var lastFilter: String? = null
private lateinit var category: String
private lateinit var predicateType: String
private var predicateId: Long = -1
private lateinit var rootView: View
private lateinit var emptyView: EmptyListView
private lateinit var data: DataProviderMixin
private lateinit var playback: PlaybackMixin
val title: String
get() {
Category.NAME_TO_TITLE[category]?.let {
return getString(it)
}
return Category.toDisplayString(app, category)
}
override fun onCreate(savedInstanceState: Bundle?) {
component.inject(this)
data = mixin(DataProviderMixin())
playback = mixin(PlaybackMixin())
mixin(ItemContextMenuMixin(appCompatActivity, contextMenuListener))
super.onCreate(savedInstanceState)
val args = arguments as Bundle
category = args.getString(Category.Extra.CATEGORY, "")
predicateType = args.getString(Category.Extra.PREDICATE_TYPE, "")
predicateId = args.getLong(Category.Extra.PREDICATE_ID, -1)
navigationType = NavigationType.get(args.getInt(Category.Extra.NAVIGATION_TYPE, NavigationType.Albums.ordinal))
adapter = CategoryBrowseAdapter(adapterListener, playback, navigationType, category, prefs)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
rootView = inflater.inflate(R.layout.recycler_view_activity, container, false)
rootView.apply {
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
val fab = findViewById<View>(R.id.fab)
val fabVisible = (category == Messages.Category.PLAYLISTS)
emptyView = findViewById(R.id.empty_list_view)
emptyView.capability = EmptyListView.Capability.OnlineOnly
emptyView.emptyMessage = getString(R.string.empty_no_items_format, categoryTypeString)
emptyView.alternateView = recyclerView
findViewById<View>(R.id.fab).setOnClickListener {
if (category == Messages.Category.PLAYLISTS) {
mixin(ItemContextMenuMixin::class.java)?.createPlaylist()
}
}
setupDefaultRecyclerView(recyclerView, adapter)
setFabVisible(fabVisible, fab, recyclerView)
}
return rootView
}
override fun onResume() {
super.onResume()
initObservers()
}
override fun setFilter(filter: String) {
this.lastFilter = filter
this.filterDebouncer.call()
}
fun createOptionsMenu(menu: Menu): Boolean {
when (Messages.Category.PLAYLISTS == category) {
true -> menu.clear()
else -> initSearchMenu(menu, this)
}
return true
}
private fun initObservers() {
disposables.add(data.provider.observeState().subscribeBy(
onNext = { states ->
when (states.first) {
IDataProvider.State.Connected -> {
filterDebouncer.cancel()
requery()
}
IDataProvider.State.Disconnected -> {
emptyView.update(states.first, adapter.itemCount)
}
else -> { }
}
},
onError = {
}))
}
private val categoryTypeString: String
get() {
Category.NAME_TO_EMPTY_TYPE[category]?.let {
return getString(it)
}
return Category.toDisplayString(app, category)
}
private fun requery() {
@Suppress("UNUSED")
data.provider
.getCategoryValues(category, predicateType, predicateId, lastFilter ?: "")
.subscribeBy(
onNext = { values -> adapter.setModel(values) },
onError = { },
onComplete = { emptyView.update(data.provider.state, adapter.itemCount)})
}
private val filterDebouncer = object : Debouncer<String>(350) {
override fun onDebounced(last: String?) {
if (!paused) {
requery()
}
}
}
private val contextMenuListener = object: ItemContextMenuMixin.EventListener() {
override fun onPlaylistDeleted(id: Long, name: String) = requery()
override fun onPlaylistUpdated(id: Long, name: String) = requery()
override fun onPlaylistCreated(id: Long, name: String) =
if (navigationType == NavigationType.Select) navigateToSelect(id, name) else requery()
}
private val adapterListener = object: CategoryBrowseAdapter.EventListener {
override fun onItemClicked(value: ICategoryValue) {
when (navigationType) {
NavigationType.Albums -> navigateToAlbums(value)
NavigationType.Tracks -> navigateToTracks(value)
NavigationType.Select -> navigateToSelect(value.id, value.value)
}
}
override fun onActionClicked(view: View, value: ICategoryValue) {
mixin(ItemContextMenuMixin::class.java)?.showForCategory(value, view)
}
}
private fun navigateToAlbums(entry: ICategoryValue) =
startActivity(AlbumBrowseActivity.getStartIntent(appCompatActivity, category, entry))
private fun navigateToTracks(entry: ICategoryValue) =
startActivity(TrackListActivity.getStartIntent(appCompatActivity, category, entry.id, entry.value))
private fun navigateToSelect(id: Long, name: String) =
appCompatActivity.run {
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(Category.Extra.CATEGORY, category)
putExtra(Category.Extra.ID, id)
putExtra(Category.Extra.NAME, name)
})
finish()
}
companion object {
fun create(context: Context,
category: String,
predicateType: String = "",
predicateId: Long = -1,
predicateValue: String = ""): CategoryBrowseFragment =
CategoryBrowseFragment().apply {
this.arguments = Bundle().apply {
putString(Category.Extra.CATEGORY, category)
putString(Category.Extra.PREDICATE_TYPE, predicateType)
putLong(Category.Extra.PREDICATE_ID, predicateId)
if (predicateValue.isNotBlank() && Category.NAME_TO_RELATED_TITLE.containsKey(category)) {
val format = Category.NAME_TO_RELATED_TITLE[category]
when (format) {
null -> throw IllegalArgumentException("unknown category $category")
else -> putString(EXTRA_ACTIVITY_TITLE, context.getString(format, predicateValue))
}
}
}
}
}
}

View File

@ -29,6 +29,8 @@ abstract class BaseActivity : AppCompatActivity(), ViewModel.Provider, Runner.Ta
}
protected var disposables = CompositeDisposable()
private set
protected lateinit var prefs: SharedPreferences
private var paused = false
private val mixins = MixinSet()

View File

@ -25,6 +25,7 @@ import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.activity.Filterable
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.ui.shared.fragment.TransportFragment
import io.casey.musikcube.remote.util.Strings
@ -40,6 +41,12 @@ fun AppCompatActivity.setupDefaultRecyclerView(
recyclerView.addItemDecoration(dividerItemDecoration)
}
fun BaseFragment.setupDefaultRecyclerView(
recyclerView: RecyclerView, adapter: RecyclerView.Adapter<*>)
{
this.appCompatActivity.setupDefaultRecyclerView(recyclerView, adapter)
}
fun RecyclerView.ViewHolder.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(itemView.context, resourceId)
@ -100,6 +107,10 @@ fun AppCompatActivity.setFabVisible(visible: Boolean, fab: View, recycler: Recyc
}
}
fun BaseFragment.setFabVisible(visible: Boolean, fab: View, recyclerView: RecyclerView) {
this.appCompatActivity.setFabVisible(visible, fab, recyclerView)
}
fun AppCompatActivity.initSearchMenu(menu: Menu, filterable: Filterable?) {
this.menuInflater.inflate(R.menu.search_menu, menu)
@ -131,6 +142,10 @@ fun AppCompatActivity.initSearchMenu(menu: Menu, filterable: Filterable?) {
searchView.setIconifiedByDefault(true)
}
fun Fragment.initSearchMenu(menu: Menu, filterable: Filterable?) {
(activity as AppCompatActivity).initSearchMenu(menu, filterable)
}
fun CheckBox.setCheckWithoutEvent(checked: Boolean,
listener: (CompoundButton, Boolean) -> Unit) {
this.setOnCheckedChangeListener(null)

View File

@ -1,19 +1,40 @@
package io.casey.musikcube.remote.ui.shared.fragment
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.ComposePathEffect
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.framework.IMixin
import io.casey.musikcube.remote.framework.MixinSet
import io.casey.musikcube.remote.framework.ViewModel
import io.casey.musikcube.remote.injection.DaggerViewComponent
import io.casey.musikcube.remote.injection.ViewComponent
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.mixin.ViewModelMixin
import io.reactivex.disposables.CompositeDisposable
open class BaseFragment: Fragment(), ViewModel.Provider {
private val mixins = MixinSet()
protected lateinit var prefs: SharedPreferences
protected val component: ViewComponent =
DaggerViewComponent.builder()
.appComponent(Application.appComponent)
.build()
protected var paused = true
private set
protected var disposables = CompositeDisposable()
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mixins.onCreate(savedInstanceState ?: Bundle())
prefs = Application.instance.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
}
override fun onStart() {
@ -23,11 +44,15 @@ open class BaseFragment: Fragment(), ViewModel.Provider {
override fun onResume() {
super.onResume()
paused = false
mixins.onResume()
}
override fun onPause() {
super.onPause()
disposables.dispose()
disposables = CompositeDisposable()
paused = true
mixins.onPause()
}
@ -55,4 +80,10 @@ open class BaseFragment: Fragment(), ViewModel.Provider {
protected fun <T: ViewModel<*>> getViewModel(): T? = mixin(ViewModelMixin::class.java)?.get<T>() as T
protected fun <T: IMixin> mixin(mixin: T): T = mixins.add(mixin)
protected fun <T: IMixin> mixin(cls: Class<out T>): T? = mixins.get(cls)
val appCompatActivity: AppCompatActivity
get() = activity as AppCompatActivity
val app: Application
get() = Application.instance
}