Merge pull request #246 from clangen/clangen/android_ui_2

Second batch of experimental Android UI refactors
This commit is contained in:
casey langen 2019-02-14 21:35:29 -08:00 committed by GitHub
commit 83cb366c3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 776 additions and 344 deletions

View File

@ -138,7 +138,7 @@ void CategoryTrackListQuery::RegularQuery(musik::core::db::Connection &db) {
std::string limitAndOffset = this->GetLimitAndOffset();
if (this->filter.size()) {
trackFilter = " AND " + category::CATEGORY_TRACKLIST_FILTER;
trackFilter = category::CATEGORY_TRACKLIST_FILTER;
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));
args.push_back(category::StringArgument(this->filter));

View File

@ -9,11 +9,11 @@ import io.casey.musikcube.remote.service.websocket.model.ICategoryValue
import io.casey.musikcube.remote.ui.albums.constant.Album
import io.casey.musikcube.remote.ui.albums.fragment.AlbumBrowseFragment
import io.casey.musikcube.remote.ui.shared.activity.FragmentActivityWithTransport
import io.casey.musikcube.remote.ui.shared.extension.EXTRA_ACTIVITY_TITLE
import io.casey.musikcube.remote.ui.shared.constant.Shared
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.util.Strings
class AlbumBrowseActivity : FragmentActivityWithTransport() {
class AlbumBrowseActivity: FragmentActivityWithTransport() {
private val albums
get() = content as AlbumBrowseFragment
@ -25,6 +25,7 @@ class AlbumBrowseActivity : FragmentActivityWithTransport() {
override fun createContentFragment(): BaseFragment =
(intent.extras ?: Bundle()).run {
AlbumBrowseFragment.create(
applicationContext,
getString(Album.Extra.CATEGORY_NAME, ""),
getLong(Album.Extra.CATEGORY_ID, -1))
}
@ -42,7 +43,7 @@ class AlbumBrowseActivity : FragmentActivityWithTransport() {
fun getStartIntent(context: Context, categoryName: String, categoryId: Long, categoryValue: String): Intent =
getStartIntent(context, categoryName, categoryId).apply {
if (Strings.notEmpty(categoryValue)) {
putExtra(EXTRA_ACTIVITY_TITLE, context.getString(R.string.albums_by_title, categoryValue))
putExtra(Shared.Extra.TITLE_OVERRIDE, context.getString(R.string.albums_by_title, categoryValue))
}
}

View File

@ -1,6 +1,8 @@
package io.casey.musikcube.remote.ui.albums.fragment
import android.content.Context
import android.os.Bundle
import android.support.v4.view.ViewCompat
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
@ -15,14 +17,15 @@ import io.casey.musikcube.remote.ui.albums.constant.Album
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.ITitleProvider
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
import io.casey.musikcube.remote.ui.shared.extension.initSearchMenu
import io.casey.musikcube.remote.ui.shared.extension.setupDefaultRecyclerView
import io.casey.musikcube.remote.ui.shared.constant.Shared
import io.casey.musikcube.remote.ui.shared.extension.*
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.ui.tracks.fragment.TrackListFragment
import io.casey.musikcube.remote.util.Debouncer
import io.reactivex.rxkotlin.subscribeBy
@ -36,7 +39,7 @@ class AlbumBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITranspo
private lateinit var emptyView: EmptyListView
override val title: String
get() = app.getString(R.string.albums_title)
get() = getTitleOverride(app.getString(R.string.albums_title))
override fun onCreate(savedInstanceState: Bundle?) {
component.inject(this)
@ -55,7 +58,9 @@ class AlbumBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITranspo
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.recycler_view_fragment, container, false).apply {
inflater.inflate(this.getLayoutId(), container, false).apply {
ViewCompat.setElevation(this, extras.elevation)
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
setupDefaultRecyclerView(recyclerView, adapter)
@ -63,6 +68,8 @@ class AlbumBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITranspo
emptyView.capability = EmptyListView.Capability.OnlineOnly
emptyView.emptyMessage = getString(R.string.empty_no_items_format, getString(R.string.browse_type_albums))
emptyView.alternateView = recyclerView
initToolbarIfNecessary(appCompatActivity, this)
}
override fun onResume() {
@ -113,9 +120,28 @@ class AlbumBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITranspo
}
private val eventListener = object: AlbumBrowseAdapter.EventListener {
override fun onItemClicked(album: IAlbum) =
startActivity(TrackListActivity.getStartIntent(
appCompatActivity, Metadata.Category.ALBUM, album.id, album.value))
override fun onItemClicked(album: IAlbum) {
when (pushContainerId > 0) {
true ->
pushWithToolbar(
pushContainerId,
"TracksForAlbum($album.id)",
TrackListFragment.create(
TrackListFragment.arguments(
appCompatActivity,
Metadata.Category.ALBUM,
album.id,
album.value))
.pushTo(pushContainerId))
false ->
startActivity(
TrackListActivity.getStartIntent(
appCompatActivity,
Metadata.Category.ALBUM,
album.id,
album.value))
}
}
override fun onActionClicked(view: View, album: IAlbum) {
mixin(ItemContextMenuMixin::class.java)?.showForCategory(album, view)
@ -124,11 +150,19 @@ class AlbumBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITranspo
companion object {
const val TAG = "AlbumBrowseFragment"
fun create(categoryName: String = "", categoryId: Long = -1L): AlbumBrowseFragment =
fun create(context: Context,
categoryName: String = "",
categoryId: Long = -1L,
categoryValue: String = ""): AlbumBrowseFragment =
AlbumBrowseFragment().apply {
arguments = Bundle().apply {
putString(Album.Extra.CATEGORY_NAME, categoryName)
putLong(Album.Extra.CATEGORY_ID, categoryId)
if (categoryValue.isNotBlank()) {
putString(
Shared.Extra.TITLE_OVERRIDE,
context.getString(R.string.albums_by_title, categoryValue))
}
}
}
}

View File

@ -3,108 +3,68 @@ package io.casey.musikcube.remote.ui.browse.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.TabLayout
import android.support.v4.view.ViewPager
import android.view.Menu
import android.view.View
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.ui.browse.adapter.BrowseFragmentAdapter
import io.casey.musikcube.remote.ui.browse.constant.Browse
import io.casey.musikcube.remote.ui.browse.fragment.BrowseFragment
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.activity.IFabConsumer
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
import io.casey.musikcube.remote.ui.shared.extension.enableUpNavigation
import io.casey.musikcube.remote.ui.shared.extension.findFragment
import io.casey.musikcube.remote.ui.shared.extension.initSearchMenu
import io.casey.musikcube.remote.ui.shared.fragment.TransportFragment
class BrowseActivity: BaseActivity(), IFilterable {
private lateinit var transport: TransportFragment
private lateinit var pager: ViewPager
private lateinit var tabs: TabLayout
private lateinit var fab: FloatingActionButton
private lateinit var adapter: BrowseFragmentAdapter
class BrowseActivity: BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.browse_activity)
adapter = BrowseFragmentAdapter(this, supportFragmentManager)
pager = findViewById(R.id.view_pager)
pager.adapter = adapter
pager.currentItem = adapter.indexOf(extras.getString(EXTRA_INITIAL_CATEGORY_TYPE))
tabs = findViewById(R.id.tab_layout)
tabs.setupWithViewPager(pager)
fab = findViewById(R.id.fab)
fab.setOnClickListener {
(adapter.fragmentAt(pager.currentItem) as? IFabConsumer)?.onFabPress(fab)
if (savedInstanceState == null) {
createFragments()
}
when (savedInstanceState == null) {
true -> createFragments()
else -> restoreFragments()
}
transport.modelChangedListener = {
adapter.onTransportChanged()
}
pager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(pos: Int, offset: Float, offsetPixels: Int) {
}
override fun onPageSelected(pos: Int) {
adapter.fragmentAt(pos)?.let {
when (it is IFabConsumer) {
true -> {
when (it.fabVisible) {
true -> fab.show()
false -> fab.hide()
}
}
false -> fab.hide()
}
findFragment<TransportFragment>(TransportFragment.TAG).modelChangedListener = {
supportFragmentManager.fragments.forEach {
if (it is ITransportObserver) {
it.onTransportChanged()
}
}
})
}
enableUpNavigation()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
return initSearchMenu(menu, this)
}
override fun setFilter(filter: String) {
adapter.filter = filter
override fun onBackPressed() {
when {
supportFragmentManager.backStackEntryCount > 0 -> {
supportFragmentManager.popBackStack()
}
else -> {
super.onBackPressed()
}
}
}
private fun createFragments() {
transport = TransportFragment.create()
supportFragmentManager
.beginTransaction()
.add(R.id.transport_container, transport, TransportFragment.TAG)
.add(
R.id.content_container,
BrowseFragment.create(extras),
BrowseFragment.TAG)
.add(
R.id.transport_container,
TransportFragment.create(),
TransportFragment.TAG)
.commit()
}
private fun restoreFragments() {
transport = findFragment(TransportFragment.TAG)
supportFragmentManager.executePendingTransactions()
}
companion object {
private const val EXTRA_INITIAL_CATEGORY_TYPE = "extra_initial_category_type"
fun getStartIntent(context: Context,
initialCategoryType: String = ""): Intent =
Intent(context, BrowseActivity::class.java).apply {
putExtra(EXTRA_INITIAL_CATEGORY_TYPE, initialCategoryType)
putExtra(Browse.Extras.INITIAL_CATEGORY_TYPE, initialCategoryType)
}
}
}

View File

@ -13,8 +13,12 @@ import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.tracks.fragment.TrackListFragment
import io.casey.musikcube.remote.service.playback.impl.remote.Metadata
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
import io.casey.musikcube.remote.ui.shared.extension.pushTo
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
class BrowseFragmentAdapter(private val context: Context, fm: FragmentManager): FragmentPagerAdapter(fm) {
class BrowseFragmentAdapter(private val context: Context,
fm: FragmentManager,
private val containerId: Int = -1): FragmentPagerAdapter(fm) {
private val fragments = mutableMapOf<Int, Fragment>()
var filter = ""
@ -25,6 +29,8 @@ class BrowseFragmentAdapter(private val context: Context, fm: FragmentManager):
}
}
var onFragmentInstantiated: ((Int) -> Unit?)? = null
fun onTransportChanged() =
fragments.forEach {
(it.value as? ITransportObserver)?.onTransportChanged()
@ -41,15 +47,17 @@ class BrowseFragmentAdapter(private val context: Context, fm: FragmentManager):
else -> 0
}
override fun getItem(index: Int): Fragment =
when (index) {
override fun getItem(index: Int): Fragment {
val fragment: BaseFragment = when (index) {
0 -> CategoryBrowseFragment.create(
CategoryBrowseFragment.arguments(context, Metadata.Category.ALBUM_ARTIST))
1 -> AlbumBrowseFragment.create()
1 -> AlbumBrowseFragment.create(context)
2 -> TrackListFragment.create()
else -> CategoryBrowseFragment.create(
CategoryBrowseFragment.arguments(Metadata.Category.PLAYLISTS, NavigationType.Tracks))
}
return fragment.pushTo(this.containerId)
}
override fun getPageTitle(position: Int): CharSequence? =
context.getString(when (position) {
@ -67,6 +75,7 @@ class BrowseFragmentAdapter(private val context: Context, fm: FragmentManager):
val result = super.instantiateItem(container, position)
fragments[position] = result as Fragment
(result as? IFilterable)?.setFilter(filter)
onFragmentInstantiated?.invoke(position)
return result
}
}

View File

@ -0,0 +1,7 @@
package io.casey.musikcube.remote.ui.browse.constant
object Browse {
object Extras {
const val INITIAL_CATEGORY_TYPE = "extra_initial_category_type"
}
}

View File

@ -0,0 +1,94 @@
package io.casey.musikcube.remote.ui.browse.fragment
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.TabLayout
import android.support.v4.view.ViewCompat
import android.support.v4.view.ViewPager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.ui.browse.adapter.BrowseFragmentAdapter
import io.casey.musikcube.remote.ui.browse.constant.Browse
import io.casey.musikcube.remote.ui.shared.activity.IFabConsumer
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.ITitleProvider
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
import io.casey.musikcube.remote.ui.shared.extension.initToolbarIfNecessary
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
class BrowseFragment: BaseFragment(), ITransportObserver, IFilterable, ITitleProvider {
private lateinit var adapter: BrowseFragmentAdapter
override val title: String
get() = getString(R.string.app_name)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.browse_fragment, container, false).apply {
val fab = findViewById<FloatingActionButton>(R.id.fab)
val pager = findViewById<ViewPager>(R.id.view_pager)
val tabs = findViewById<TabLayout>(R.id.tab_layout)
val showFabIfNecessary = { pos: Int ->
adapter.fragmentAt(pos)?.let {
when (it is IFabConsumer) {
true -> {
when (it.fabVisible) {
true -> fab.show()
false -> fab.hide()
}
}
false -> fab.hide()
}
}
}
fab.setOnClickListener {
(adapter.fragmentAt(pager.currentItem) as? IFabConsumer)?.onFabPress(fab)
}
adapter = BrowseFragmentAdapter(appCompatActivity, childFragmentManager, R.id.content_container)
adapter.onFragmentInstantiated = { pos ->
if (pos == pager.currentItem) {
showFabIfNecessary(pos)
}
}
pager.adapter = adapter
pager.currentItem = adapter.indexOf(extras.getString(Browse.Extras.INITIAL_CATEGORY_TYPE))
tabs.setupWithViewPager(pager)
pager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(pos: Int, offset: Float, offsetPixels: Int) {
}
override fun onPageSelected(pos: Int) {
showFabIfNecessary(pos)
}
})
initToolbarIfNecessary(appCompatActivity, this)
showFabIfNecessary(pager.currentItem)
}
override fun onTransportChanged() =
adapter.onTransportChanged()
override fun setFilter(filter: String) {
adapter.filter = filter
}
companion object {
const val TAG = "BrowseFragment"
fun create(extras: Bundle): BrowseFragment =
BrowseFragment().apply { arguments = extras }
}
}

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.v4.view.ViewCompat
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
@ -15,6 +16,7 @@ import io.casey.musikcube.remote.service.playback.impl.remote.Metadata
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.albums.fragment.AlbumBrowseFragment
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
@ -22,36 +24,45 @@ import io.casey.musikcube.remote.ui.shared.activity.IFabConsumer
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.ITitleProvider
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
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.setupDefaultRecyclerView
import io.casey.musikcube.remote.ui.shared.constant.Shared
import io.casey.musikcube.remote.ui.shared.extension.*
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.ui.tracks.fragment.TrackListFragment
import io.casey.musikcube.remote.util.Debouncer
import io.reactivex.rxkotlin.subscribeBy
class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITransportObserver, IFabConsumer {
private lateinit var adapter: CategoryBrowseAdapter
private var navigationType: NavigationType = NavigationType.Albums
private var lastFilter: String? = null
private var category: String = ""
private 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
private val navigationType: NavigationType
get() = NavigationType.get(extras.getInt(
Category.Extra.NAVIGATION_TYPE, NavigationType.Albums.ordinal))
private val category
get() = extras.getString(Category.Extra.CATEGORY, "")
private val predicateType: String
get() = extras.getString(Category.Extra.PREDICATE_TYPE, "")
private val predicateId: Long
get() = extras.getLong(Category.Extra.PREDICATE_ID, -1L)
override val title: String
get() {
Category.NAME_TO_TITLE[category]?.let {
return getString(it)
return getTitleOverride(getString(it))
}
return Category.toDisplayString(app, category)
return getTitleOverride(Category.toDisplayString(app, category))
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -62,18 +73,13 @@ class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITran
playback = mixin(PlaybackMixin())
mixin(ItemContextMenuMixin(appCompatActivity, contextMenuListener, this))
extras.run {
category = getString(Category.Extra.CATEGORY, category)
predicateType = getString(Category.Extra.PREDICATE_TYPE, predicateType)
predicateId = getLong(Category.Extra.PREDICATE_ID, predicateId)
navigationType = NavigationType.get(getInt(Category.Extra.NAVIGATION_TYPE, navigationType.ordinal))
}
adapter = CategoryBrowseAdapter(adapterListener, playback, navigationType, category, prefs)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.recycler_view_fragment, container, false).apply {
inflater.inflate(this.getLayoutId(), container, false).apply {
ViewCompat.setElevation(this, extras.elevation)
this@CategoryBrowseFragment.rootView = this
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
@ -84,6 +90,7 @@ class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITran
emptyView.alternateView = recyclerView
setupDefaultRecyclerView(recyclerView, adapter)
initToolbarIfNecessary(appCompatActivity, this)
}
override fun onFabPress(fab: FloatingActionButton) {
@ -188,10 +195,33 @@ class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITran
}
private fun navigateToAlbums(entry: ICategoryValue) =
startActivity(AlbumBrowseActivity.getStartIntent(appCompatActivity, category, entry))
when (pushContainerId > 0) {
true ->
this.pushWithToolbar(
pushContainerId,
"AlbumsBy($entry.value)",
AlbumBrowseFragment
.create(app, entry.type, entry.id, entry.value)
.pushTo(pushContainerId))
false ->
startActivity(AlbumBrowseActivity
.getStartIntent(appCompatActivity, category, entry))
}
private fun navigateToTracks(entry: ICategoryValue) =
startActivity(TrackListActivity.getStartIntent(appCompatActivity, category, entry.id, entry.value))
when (this.pushContainerId > 0) {
true ->
this.pushWithToolbar(
this.pushContainerId,
"TracksBy($entry.value)",
TrackListFragment.create(TrackListFragment
.arguments(appCompatActivity, entry.type, entry.id))
.pushTo(pushContainerId))
false ->
startActivity(TrackListActivity.getStartIntent(
appCompatActivity, category, entry.id, entry.value))
}
private fun navigateToSelect(id: Long, name: String) =
appCompatActivity.run {
@ -231,7 +261,7 @@ class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITran
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))
else -> putString(Shared.Extra.TITLE_OVERRIDE, context.getString(format, predicateValue))
}
}
}

View File

@ -3,10 +3,9 @@ package io.casey.musikcube.remote.ui.shared.activity
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.view.Menu
import android.view.MenuItem
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.ui.shared.extension.enableUpNavigation
import io.casey.musikcube.remote.ui.shared.extension.findFragment
import io.casey.musikcube.remote.ui.shared.extension.setTitleFromIntent
import io.casey.musikcube.remote.ui.shared.extension.*
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.ui.shared.fragment.TransportFragment
@ -49,15 +48,32 @@ abstract class FragmentActivityWithTransport: BaseActivity(), IFilterable {
override fun onResume() {
super.onResume()
(content as? ITitleProvider)?.run {
setTitleFromIntent(this.title)
if (!content.hasToolbar) {
(content as? ITitleProvider)?.run {
setTitleFromIntent(this.title)
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean =
(content as? IMenuProvider)?.run {
return this.createOptionsMenu(menu)
} ?: false
override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (!content.hasToolbar) {
(content as? IMenuProvider)?.run {
return this.createOptionsMenu(menu)
}
}
return false
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (!content.hasToolbar) {
(content as? IMenuProvider)?.run {
if (this.optionsItemSelected(item)) {
return true
}
}
}
return super.onOptionsItemSelected(item)
}
override fun setFilter(filter: String) =
(content as? IFilterable)?.run {
@ -73,6 +89,8 @@ abstract class FragmentActivityWithTransport: BaseActivity(), IFilterable {
private fun createFragments() {
content = createContentFragment()
.withToolbar()
.withTitleOverride(this)
transport = TransportFragment.create()
supportFragmentManager
.beginTransaction()

View File

@ -1,7 +1,9 @@
package io.casey.musikcube.remote.ui.shared.activity
import android.view.Menu
import android.view.MenuItem
interface IMenuProvider {
fun createOptionsMenu(menu: Menu): Boolean
fun optionsItemSelected(menuItem: MenuItem): Boolean
}

View File

@ -0,0 +1,10 @@
package io.casey.musikcube.remote.ui.shared.constant
object Shared {
object Extra {
const val TITLE_OVERRIDE = "extra_title_override"
const val WITH_TOOLBAR = "extra_with_toolbar"
const val PUSH_CONTAINER_ID = "extra_push_container_id"
const val ELEVATION = "extra_elevation"
}
}

View File

@ -3,6 +3,7 @@ package io.casey.musikcube.remote.ui.shared.extension
import android.app.SearchManager
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.app.DialogFragment
import android.support.v4.app.Fragment
@ -23,14 +24,47 @@ 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.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.IMenuProvider
import io.casey.musikcube.remote.ui.shared.constant.Shared
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
const val EXTRA_ACTIVITY_TITLE = "extra_title"
/*
*
* SharedPreferences
*
*/
fun SharedPreferences.getString(key: String): String? =
when (!this.contains(key)) {
true -> null
else -> this.getString(key, "")
}
/*
*
* Toolbar
*
*/
fun Toolbar.initSearchMenu(activity: AppCompatActivity, filterable: IFilterable?): Boolean =
activity.initSearchMenu(this.menu, filterable)
fun Toolbar.setTitleFromIntent(defaultTitle: String) {
val extras = (context as? AppCompatActivity)?.intent?.extras ?: Bundle()
val title = extras.getString(Shared.Extra.TITLE_OVERRIDE)
this.title = if (Strings.notEmpty(title)) title else defaultTitle
}
/*
*
* AppCompatActivity
*
*/
fun AppCompatActivity.setupDefaultRecyclerView(
recyclerView: RecyclerView, adapter: RecyclerView.Adapter<*>)
recyclerView: RecyclerView, adapter: RecyclerView.Adapter<*>)
{
val layoutManager = LinearLayoutManager(this)
val dividerItemDecoration = DividerItemDecoration(this, layoutManager.orientation)
@ -39,23 +73,6 @@ 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)
fun View.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(context, resourceId)
fun Fragment.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(activity!!, resourceId)
fun AppCompatActivity.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(this, resourceId)
val AppCompatActivity.toolbar: Toolbar?
get() = findViewById(R.id.toolbar)
@ -74,9 +91,9 @@ fun AppCompatActivity.addTransportFragment(
val fragment = TransportFragment.create()
this.supportFragmentManager
.beginTransaction()
.add(R.id.transport_container, fragment, TransportFragment.TAG)
.commit()
.beginTransaction()
.add(R.id.transport_container, fragment, TransportFragment.TAG)
.commit()
fragment.modelChangedListener = listener
return fragment
@ -86,10 +103,10 @@ fun AppCompatActivity.addTransportFragment(
}
fun AppCompatActivity.setTitleFromIntent(defaultId: Int) =
this.setTitleFromIntent(getString(defaultId))
this.setTitleFromIntent(getString(defaultId))
fun AppCompatActivity.setTitleFromIntent(defaultTitle: String) {
val title = this.intent.getStringExtra(EXTRA_ACTIVITY_TITLE)
val title = this.intent.getStringExtra(Shared.Extra.TITLE_OVERRIDE)
this.title = if (Strings.notEmpty(title)) title else defaultTitle
}
@ -126,56 +143,144 @@ fun AppCompatActivity.initSearchMenu(menu: Menu, filterable: IFilterable?): Bool
return true
}
fun Fragment.initSearchMenu(menu: Menu, filterable: IFilterable?): Boolean =
(activity as AppCompatActivity).initSearchMenu(menu, filterable)
fun CheckBox.setCheckWithoutEvent(checked: Boolean,
listener: (CompoundButton, Boolean) -> Unit) {
this.setOnCheckedChangeListener(null)
this.isChecked = checked
this.setOnCheckedChangeListener(listener)
}
fun EditText.setTextAndMoveCursorToEnd(text: String) {
this.setText(text)
this.setSelection(this.text.length)
}
fun View.setVisible(visible: Boolean) {
this.visibility = if (visible) View.VISIBLE else View.GONE
}
fun AppCompatActivity.dpToPx(dp: Float): Float = dp * this.resources.displayMetrics.density
fun showKeyboard(context: Context) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
}
fun hideKeyboard(context: Context, view: View) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
fun AppCompatActivity.showKeyboard() = showKeyboard(this)
fun AppCompatActivity.hideKeyboard(view: View? = null) {
val v = view ?: this.findViewById(android.R.id.content)
hideKeyboard(this, v)
}
fun DialogFragment.showKeyboard() = showKeyboard(activity!!)
fun DialogFragment.hideKeyboard() =
hideKeyboard(activity!!, activity!!.findViewById(android.R.id.content))
fun AppCompatActivity.dialogVisible(tag: String): Boolean =
this.supportFragmentManager.findFragmentByTag(tag) != null
this.supportFragmentManager.findFragmentByTag(tag) != null
fun AppCompatActivity.showDialog(dialog: DialogFragment, tag: String) {
dialog.show(this.supportFragmentManager, tag)
}
fun AppCompatActivity.slideNextUp() = overridePendingTransition(R.anim.slide_up, R.anim.stay_put)
fun AppCompatActivity.slideThisDown() = overridePendingTransition(R.anim.stay_put, R.anim.slide_down)
fun AppCompatActivity.slideNextLeft() = overridePendingTransition(R.anim.slide_left, R.anim.slide_left_bg)
fun AppCompatActivity.slideThisRight() = overridePendingTransition(R.anim.slide_right_bg, R.anim.slide_right)
inline fun <reified T> AppCompatActivity.findFragment(tag: String): T {
return this.supportFragmentManager.find(tag)
}
/*
*
* Bundle
*
*/
fun Bundle.withElevation(fm: FragmentManager): Bundle {
this.putFloat(Shared.Extra.ELEVATION, (fm.backStackEntryCount + 1) * 16.0f)
return this
}
val Bundle.elevation: Float
get() = this.getFloat(Shared.Extra.ELEVATION, 0.0f)
/*
*
* BaseFragment
*
*/
val BaseFragment.pushContainerId: Int
get() = this.extras.getInt(Shared.Extra.PUSH_CONTAINER_ID, -1)
inline fun <reified T: BaseFragment> T.pushTo(containerId: Int): T {
if (containerId > 0) {
this.extras.putInt(Shared.Extra.PUSH_CONTAINER_ID, containerId)
}
return this
}
inline fun <reified T: BaseFragment> T.pushTo(other: BaseFragment): T {
this.pushTo(other.pushContainerId)
return this
}
fun BaseFragment.pushWithToolbar(containerId: Int, backstackId: String, fragment: BaseFragment) {
appCompatActivity.supportFragmentManager
.beginTransaction()
.setCustomAnimations(
R.anim.slide_left, R.anim.slide_left_bg,
R.anim.slide_right_bg, R.anim.slide_right)
.replace(
containerId,
fragment
.withToolbar()
.addElevation(appCompatActivity.supportFragmentManager))
.addToBackStack(backstackId)
.commit()
}
inline fun <reified T: BaseFragment> T.withToolbar(): T {
this.arguments?.putBoolean(Shared.Extra.WITH_TOOLBAR, true)
return this
}
inline fun <reified T: BaseFragment> T.withTitleOverride(activity: AppCompatActivity): T {
activity.intent?.getStringExtra(Shared.Extra.TITLE_OVERRIDE)?.let {
if (it.isNotEmpty()) {
this.extras.putString(Shared.Extra.TITLE_OVERRIDE, it)
}
}
return this
}
fun BaseFragment.initSearchMenu(menu: Menu, filterable: IFilterable?): Boolean =
(activity as AppCompatActivity).initSearchMenu(menu, filterable)
fun BaseFragment.initToolbarIfNecessary(activity: AppCompatActivity, view: View, searchMenu: Boolean = true) {
view.findViewById<Toolbar>(R.id.toolbar)?.let {
it.navigationIcon = appCompatActivity.getDrawable(R.drawable.ic_back)
it.setNavigationOnClickListener {
appCompatActivity.onBackPressed()
}
if (searchMenu) {
it.initSearchMenu(activity, this as? IFilterable)
}
if (this is IMenuProvider) {
this.createOptionsMenu(it.menu)
it.setOnMenuItemClickListener {
menuItem -> this.optionsItemSelected(menuItem)
}
}
}
}
fun BaseFragment.getLayoutId(): Int =
when (this.extras.getBoolean(Shared.Extra.WITH_TOOLBAR)) {
true -> R.layout.recycler_view_with_empty_state_and_toolbar_and_fab
else -> R.layout.recycler_view_with_empty_state
}
inline fun <reified T: BaseFragment> T.addElevation(fm: FragmentManager): T {
this.extras.withElevation(fm)
return this
}
fun BaseFragment.setupDefaultRecyclerView(
recyclerView: RecyclerView, adapter: RecyclerView.Adapter<*>)
{
this.appCompatActivity.setupDefaultRecyclerView(recyclerView, adapter)
}
fun BaseFragment.getTitleOverride(defaultId: Int): String =
this.getTitleOverride(getString(defaultId))
fun BaseFragment.getTitleOverride(defaultTitle: String): String {
val title = this.extras.getString(Shared.Extra.TITLE_OVERRIDE) ?: ""
return if (Strings.notEmpty(title)) title else defaultTitle
}
/*
*
* Snackbar
*
*/
fun showSnackbar(view: View, text: String, bgColor: Int, fgColor: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) {
val sb = Snackbar.make(view, text, Snackbar.LENGTH_LONG)
@ -213,24 +318,89 @@ fun AppCompatActivity.showSnackbar(stringId: Int, buttonText: String? = null, bu
showSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
fun AppCompatActivity.showSnackbar(stringId: String, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
showSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
fun AppCompatActivity.showSnackbar(viewId: Int, stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(this.findViewById<View>(viewId), stringId, buttonText, buttonCb)
fun fallback(input: String?, fallback: String): String =
if (input.isNullOrEmpty()) fallback else input
/*
*
* View 1-offs
*
*/
fun fallback(input: String?, fallback: Int): String =
if (input.isNullOrEmpty()) Application.instance.getString(fallback) else input
fun CheckBox.setCheckWithoutEvent(checked: Boolean,
listener: (CompoundButton, Boolean) -> Unit) {
this.setOnCheckedChangeListener(null)
this.isChecked = checked
this.setOnCheckedChangeListener(listener)
}
fun AppCompatActivity.slideNextUp() = overridePendingTransition(R.anim.slide_up, R.anim.stay_put)
fun EditText.setTextAndMoveCursorToEnd(text: String) {
this.setText(text)
this.setSelection(this.text.length)
}
fun AppCompatActivity.slideThisDown() = overridePendingTransition(R.anim.stay_put, R.anim.slide_down)
fun View.setVisible(visible: Boolean) {
this.visibility = if (visible) View.VISIBLE else View.GONE
}
fun AppCompatActivity.slideNextLeft() = overridePendingTransition(R.anim.slide_left, R.anim.slide_left_bg)
inline fun <reified T> FragmentManager.find(tag: String): T {
return findFragmentByTag(tag) as T
}
fun AppCompatActivity.slideThisRight() = overridePendingTransition(R.anim.slide_right_bg, R.anim.slide_right)
/*
*
* Colors
*
*/
fun RecyclerView.ViewHolder.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(itemView.context, resourceId)
fun View.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(context, resourceId)
fun Fragment.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(activity!!, resourceId)
fun AppCompatActivity.getColorCompat(resourceId: Int): Int =
ContextCompat.getColor(this, resourceId)
/*
*
* Keyboard
*
*/
fun showKeyboard(context: Context) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
}
fun hideKeyboard(context: Context, view: View) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
fun AppCompatActivity.showKeyboard() = showKeyboard(this)
fun AppCompatActivity.hideKeyboard(view: View? = null) {
val v = view ?: this.findViewById(android.R.id.content)
hideKeyboard(this, v)
}
fun DialogFragment.showKeyboard() =
showKeyboard(activity!!)
fun DialogFragment.hideKeyboard() =
hideKeyboard(activity!!, activity!!.findViewById(android.R.id.content))
/*
*
* misc
*
*/
fun <T1: Any, T2: Any, R: Any> letMany(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
@ -249,8 +419,8 @@ fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> letMany(p1: T1?, p2: T
fun titleEllipsizeMode(prefs: SharedPreferences): TextUtils.TruncateAt {
val modeIndex = prefs.getInt(
Prefs.Key.TITLE_ELLIPSIS_MODE_INDEX,
Prefs.Default.TITLE_ELLIPSIS_SIZE_INDEX)
Prefs.Key.TITLE_ELLIPSIS_MODE_INDEX,
Prefs.Default.TITLE_ELLIPSIS_SIZE_INDEX)
return when(modeIndex) {
0 -> TextUtils.TruncateAt.START
@ -259,16 +429,8 @@ fun titleEllipsizeMode(prefs: SharedPreferences): TextUtils.TruncateAt {
}
}
fun SharedPreferences.getString(key: String): String? =
when (!this.contains(key)) {
true -> null
else -> this.getString(key, "")
}
fun fallback(input: String?, fallback: String): String =
if (input.isNullOrEmpty()) fallback else input
inline fun <reified T> FragmentManager.find(tag: String): T {
return findFragmentByTag(tag) as T
}
inline fun <reified T> AppCompatActivity.findFragment(tag: String): T {
return this.supportFragmentManager.find(tag)
}
fun fallback(input: String?, fallback: Int): String =
if (input.isNullOrEmpty()) Application.instance.getString(fallback) else input

View File

@ -7,6 +7,8 @@ import android.os.Bundle
import android.os.Handler
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.animation.Animation
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.framework.IMixin
import io.casey.musikcube.remote.framework.MixinSet
@ -14,8 +16,15 @@ 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.activity.ITitleProvider
import io.casey.musikcube.remote.ui.shared.extension.setTitleFromIntent
import io.casey.musikcube.remote.ui.shared.mixin.ViewModelMixin
import io.reactivex.disposables.CompositeDisposable
import android.view.animation.AlphaAnimation
import android.view.animation.AnimationUtils
import io.casey.musikcube.remote.R
import java.lang.Exception
open class BaseFragment: Fragment(), ViewModel.Provider {
private val mixins = MixinSet()
@ -51,6 +60,9 @@ open class BaseFragment: Fragment(), ViewModel.Provider {
super.onResume()
paused = false
mixins.onResume()
if (this is ITitleProvider) {
toolbar?.setTitleFromIntent(title)
}
}
override fun onPause() {
@ -81,17 +93,69 @@ open class BaseFragment: Fragment(), ViewModel.Provider {
mixins.onDestroy()
}
/* https://stackoverflow.com/a/23276145 */
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
val parent = parentFragment
/* only apply for childFragmentManager transitions */
return when (!enter && parent != null && parent.isRemoving) {
true -> {
/* this is a workaround for the bug where child fragments disappear when
the parent is removed (as all children are first removed from the parent)
See https://code.google.com/p/android/issues/detail?id=55228 */
val doNothingAnim = AlphaAnimation(1f, 1f)
doNothingAnim.duration = getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION)
doNothingAnim
}
false -> {
super.onCreateAnimation(transit, enter, nextAnim)
}
}
}
override fun <T: ViewModel<*>> createViewModel(): T? = null
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)
protected val extras: Bundle
get() = arguments ?: Bundle()
val hasToolbar: Boolean
get() = this.toolbar != null
val toolbar: Toolbar?
get() = this.view?.findViewById(R.id.toolbar)
val extras: Bundle
get() {
if (arguments == null) {
arguments = Bundle()
}
return arguments!!
}
val appCompatActivity: AppCompatActivity
get() = activity as AppCompatActivity
val app: Application
get() = Application.instance
companion object {
private const val DEFAULT_CHILD_ANIMATION_DURATION = 250L
private fun getNextAnimationDuration(fragment: Fragment, defValue: Long): Long =
try {
/* attempt to get the resource ID of the next animation that
will be applied to the given fragment. */
val nextAnimField = Fragment::class.java.getDeclaredField("mNextAnim")
nextAnimField.isAccessible = true
val nextAnimResource = nextAnimField.getInt(fragment)
val nextAnim = AnimationUtils.loadAnimation(fragment.activity, nextAnimResource)
when (nextAnim == null) {
true -> defValue
false -> nextAnim.duration
}
}
catch (ex: Exception) {
defValue
}
}
}

View File

@ -28,7 +28,7 @@ class TransportFragment: BaseFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
this.rootView = inflater.inflate(R.layout.fragment_transport, container, false)
this.rootView = inflater.inflate(R.layout.transport_fragment, container, false)
bindEventHandlers()
rebindUi()
return this.rootView

View File

@ -7,12 +7,11 @@ import android.view.MenuItem
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.playback.impl.remote.Metadata
import io.casey.musikcube.remote.ui.shared.activity.FragmentActivityWithTransport
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.fragment.BaseFragment
import io.casey.musikcube.remote.ui.tracks.constant.Track
import io.casey.musikcube.remote.ui.tracks.fragment.TrackListFragment
class TrackListActivity : FragmentActivityWithTransport(), IFilterable {
class TrackListActivity: FragmentActivityWithTransport() {
private val tracks
get() = content as TrackListFragment

View File

@ -3,6 +3,7 @@ package io.casey.musikcube.remote.ui.tracks.fragment
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.view.ViewCompat
import android.support.v7.app.AppCompatActivity
import android.view.*
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
@ -13,12 +14,11 @@ import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.websocket.model.ITrackListQueryFactory
import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.ui.shared.activity.IFilterable
import io.casey.musikcube.remote.ui.shared.activity.IMenuProvider
import io.casey.musikcube.remote.ui.shared.activity.ITitleProvider
import io.casey.musikcube.remote.ui.shared.activity.ITransportObserver
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.setupDefaultRecyclerView
import io.casey.musikcube.remote.ui.shared.extension.showSnackbar
import io.casey.musikcube.remote.ui.shared.constant.Shared
import io.casey.musikcube.remote.ui.shared.extension.*
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
@ -35,7 +35,7 @@ import io.casey.musikcube.remote.util.Strings
import io.reactivex.Observable
import io.reactivex.rxkotlin.subscribeBy
class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransportObserver {
class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransportObserver, IMenuProvider {
private lateinit var tracks: DefaultSlidingWindow
private lateinit var emptyView: EmptyListView
private lateinit var adapter: TrackListAdapter
@ -100,13 +100,16 @@ class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransport
}))
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.recycler_view_fragment, container, false).apply {
inflater.inflate(this.getLayoutId(), container, false).apply {
ViewCompat.setElevation(this, extras.elevation)
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
tracks = DefaultSlidingWindow(recyclerView, data.provider, queryFactory)
adapter = TrackListAdapter(tracks, eventListener, playback, prefs)
setupDefaultRecyclerView(recyclerView, adapter)
initToolbarIfNecessary(appCompatActivity, this, searchMenu = false)
emptyView = findViewById(R.id.empty_list_view)
@ -119,37 +122,7 @@ class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransport
tracks.setOnMetadataLoadedListener(slidingWindowListener)
}
override val title: String
get() = getString(titleId)
override fun setFilter(filter: String) {
lastFilter = filter
filterDebouncer.call()
}
fun createOptionsMenu(menu: Menu): Boolean {
when (Metadata.Category.PLAYLISTS == categoryType) {
true -> appCompatActivity.menuInflater.inflate(R.menu.view_playlist_menu, menu)
false -> initSearchMenu(menu, this)
}
return true
}
fun optionsItemSelected(item: MenuItem): Boolean =
when (item.itemId == R.id.action_edit) {
true -> {
appCompatActivity.startActivityForResult(
EditPlaylistActivity.getStartIntent(
appCompatActivity,
categoryValue,
categoryId),
Track.RequestCode.EDIT_PLAYLIST)
true
}
else -> false
}
fun activityResult(requestCode: Int, resultCode: Int, data: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Track.RequestCode.EDIT_PLAYLIST && resultCode == AppCompatActivity.RESULT_OK && data != null) {
val playlistName = data.getStringExtra(EditPlaylistActivity.EXTRA_PLAYLIST_NAME) ?: ""
val playlistId = data.getLongExtra(EditPlaylistActivity.EXTRA_PLAYLIST_ID, -1L)
@ -168,6 +141,36 @@ class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransport
super.onActivityResult(requestCode, resultCode, data)
}
override val title: String
get() = getTitleOverride(getString(titleId))
override fun setFilter(filter: String) {
lastFilter = filter
filterDebouncer.call()
}
override fun createOptionsMenu(menu: Menu): Boolean {
when (Metadata.Category.PLAYLISTS == categoryType) {
true -> appCompatActivity.menuInflater.inflate(R.menu.view_playlist_menu, menu)
false -> initSearchMenu(menu, this)
}
return true
}
override fun optionsItemSelected(menuItem: MenuItem): Boolean =
when (menuItem.itemId == R.id.action_edit) {
true -> {
appCompatActivity.startActivityForResult(
EditPlaylistActivity.getStartIntent(
appCompatActivity,
categoryValue,
categoryId),
Track.RequestCode.EDIT_PLAYLIST)
true
}
else -> false
}
override fun onTransportChanged() {
adapter.notifyDataSetChanged()
}
@ -283,7 +286,7 @@ class TrackListFragment: BaseFragment(), IFilterable, ITitleProvider, ITransport
putString(Track.Extra.CATEGORY_VALUE, categoryValue)
if (Strings.notEmpty(categoryValue)) {
putString(
EXTRA_ACTIVITY_TITLE,
Shared.Extra.TITLE_OVERRIDE,
context.getString(R.string.songs_from_category, categoryValue))
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorControlNormal" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -1,58 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_primary" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_gravity="bottom|right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_padding"
android:src="@drawable/ic_fab_add"
android:scaleType="center"
android:elevation="6dp"
android:visibility="gone"
app:layout_anchor="@id/view_pager"
app:layout_anchorGravity="bottom|right"
app:backgroundTint="@color/color_primary"
app:fabSize="mini"
app:rippleColor="?colorAccent"/>
</android.support.design.widget.CoordinatorLayout>
android:layout_weight="1.0" />
<FrameLayout
android:id="@+id/transport_container"

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_primary" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_gravity="bottom|right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_padding"
android:src="@drawable/ic_fab_add"
android:scaleType="center"
android:elevation="6dp"
android:visibility="gone"
app:layout_anchor="@id/view_pager"
app:layout_anchorGravity="bottom|right"
app:backgroundTint="@color/color_primary"
app:fabSize="mini"
app:rippleColor="?colorAccent"/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -23,6 +23,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<io.casey.musikcube.remote.ui.shared.view.EmptyListView
style="@style/EmptyView"
android:id="@+id/empty_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -1,40 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_weight="1"
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="0dp">
<include layout="@layout/toolbar" />
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_gravity="bottom|right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_padding"
android:src="@drawable/ic_fab_add"
android:scaleType="center"
android:elevation="6dp"
android:visibility="gone"
app:layout_anchor="@id/content_container"
app:layout_anchorGravity="bottom|right"
app:backgroundTint="@color/color_primary"
app:fabSize="mini"
app:rippleColor="?colorAccent"/>
</android.support.design.widget.CoordinatorLayout>
android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/transport_container"

View File

@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/theme_background" >
<android.support.design.widget.CoordinatorLayout
android:layout_weight="1"
@ -23,6 +24,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<io.casey.musikcube.remote.ui.shared.view.EmptyListView
style="@style/EmptyView"
android:id="@+id/empty_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -30,20 +32,9 @@
android:visibility="invisible"/>
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/fab"
android:layout_gravity="bottom|right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_padding"
android:src="@drawable/ic_fab_add"
android:scaleType="center"
android:elevation="6dp"
android:visibility="gone"
app:layout_anchor="@id/recycler_view"
app:layout_anchorGravity="bottom|right"
app:backgroundTint="@color/color_primary"
app:fabSize="mini"
app:rippleColor="?colorAccent"/>
app:layout_anchor="@id/recycler_view" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -3,18 +3,18 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/theme_background"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
style="@style/RecyclerView"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastScrollAutoHide="true"
app:fastScrollAutoHideDelay="1500"
app:fastScrollThumbInactiveColor="@color/color_primary_dark"
app:fastScrollThumbColor="@color/color_accent" />
android:layout_height="match_parent" />
<io.casey.musikcube.remote.ui.shared.view.EmptyListView
style="@style/EmptyView"
android:id="@+id/empty_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@color/theme_background" >
<include layout="@layout/toolbar" />
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
style="@style/RecyclerView"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<io.casey.musikcube.remote.ui.shared.view.EmptyListView
style="@style/EmptyView"
android:id="@+id/empty_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
</FrameLayout>
<android.support.design.widget.FloatingActionButton
style="@style/FAB"
android:id="@+id/fab"
app:layout_anchor="@id/content_container" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -46,7 +46,6 @@
</LinearLayout>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="2dp"/>
@ -88,5 +87,4 @@
</FrameLayout>
</LinearLayout>

View File

@ -87,4 +87,30 @@
<item name="android:shadowRadius">3</item>
</style>
<style name="FAB">
<item name="android:layout_gravity">bottom|right</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">@dimen/fab_padding</item>
<item name="android:src">@drawable/ic_fab_add</item>
<item name="android:scaleType">center</item>
<item name="android:elevation">6dp</item>
<item name="android:visibility">gone</item>
<item name="layout_anchorGravity">bottom|right</item>
<item name="backgroundTint">@color/color_primary</item>
<item name="fabSize">mini</item>
<item name="rippleColor">?colorAccent</item>
</style>
<style name="RecyclerView">
<item name="fastScrollAutoHide">true</item>
<item name="fastScrollAutoHideDelay">1500</item>
<item name="fastScrollThumbInactiveColor">@color/color_primary_dark</item>
<item name="fastScrollThumbColor">@color/color_accent</item>
</style>
<style name="EmptyView">
<item name="android:background">@color/theme_background</item>
</style>
</resources>