Added empty state
This commit is contained in:
commit
b66184787d
18 changed files with 305 additions and 135 deletions
|
@ -43,7 +43,7 @@ class App : MultiDexApplication() {
|
|||
// default theme
|
||||
if (!ThemeStore.isConfigured(this, 3)) {
|
||||
ThemeStore.editTheme(this)
|
||||
.accentColorRes(R.color.md_green_A200)
|
||||
.accentColorRes(R.color.md_deep_purple_A200)
|
||||
.coloredNavigationBar(true)
|
||||
.commit()
|
||||
}
|
||||
|
|
|
@ -1,120 +1,129 @@
|
|||
package code.name.monkey.retromusic.fragments.base
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.StringRes
|
||||
import android.view.*
|
||||
import androidx.annotation.*
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.util.DensityUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import code.name.monkey.retromusic.util.*
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
import kotlinx.android.synthetic.main.fragment_main_activity_recycler_view.*
|
||||
|
||||
abstract class AbsLibraryPagerRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : RecyclerView.LayoutManager> : AbsLibraryPagerFragment(), AppBarLayout.OnOffsetChangedListener {
|
||||
|
||||
protected var adapter: A? = null
|
||||
protected var layoutManager: LM? = null
|
||||
protected var adapter: A? = null
|
||||
protected var layoutManager: LM? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(
|
||||
R.layout.fragment_main_activity_recycler_view, container, false
|
||||
);
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_main_activity_recycler_view, container, false);
|
||||
return view
|
||||
}
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
libraryFragment.addOnAppBarOffsetChangedListener(this)
|
||||
initLayoutManager()
|
||||
initAdapter()
|
||||
setUpRecyclerView()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
libraryFragment.addOnAppBarOffsetChangedListener(this)
|
||||
initLayoutManager()
|
||||
initAdapter()
|
||||
setUpRecyclerView()
|
||||
}
|
||||
private fun setUpRecyclerView() {
|
||||
if (recyclerView is FastScrollRecyclerView) {
|
||||
ViewUtil.setUpFastScrollRecyclerViewColor(
|
||||
requireActivity(), recyclerView as FastScrollRecyclerView
|
||||
)
|
||||
}
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun setUpRecyclerView() {
|
||||
if (recyclerView is FastScrollRecyclerView) {
|
||||
ViewUtil.setUpFastScrollRecyclerViewColor(requireActivity(), recyclerView as FastScrollRecyclerView)
|
||||
}
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
private fun initAdapter() {
|
||||
adapter = createAdapter()
|
||||
adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
checkIsEmpty()
|
||||
checkForPadding()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
adapter = createAdapter()
|
||||
adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
checkIsEmpty()
|
||||
checkForPadding()
|
||||
}
|
||||
})
|
||||
}
|
||||
protected open val emptyMessage: Int
|
||||
@StringRes get() = R.string.empty
|
||||
|
||||
protected open val emptyMessage: Int
|
||||
@StringRes
|
||||
get() = R.string.empty
|
||||
private fun getEmojiByUnicode(unicode: Int): String {
|
||||
return String(Character.toChars(unicode))
|
||||
}
|
||||
|
||||
private fun checkIsEmpty() {
|
||||
emptyText.setText(emptyMessage)
|
||||
empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
private fun checkIsEmpty() {
|
||||
emptyEmoji.text = getEmojiByUnicode(0x1F631)
|
||||
emptyText.setText(emptyMessage)
|
||||
empty.visibility = if (adapter!!.itemCount == 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun checkForPadding() {
|
||||
val itemCount: Int = adapter?.itemCount ?: 0
|
||||
val params = container.layoutParams as ViewGroup.MarginLayoutParams
|
||||
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
val height = DensityUtil.dip2px(requireContext(), 104f)
|
||||
params.bottomMargin = height
|
||||
} else {
|
||||
val height = DensityUtil.dip2px(requireContext(), 52f)
|
||||
params.bottomMargin = height
|
||||
}
|
||||
}
|
||||
private fun checkForPadding() {
|
||||
val itemCount: Int = adapter?.itemCount ?: 0
|
||||
val params = container.layoutParams as ViewGroup.MarginLayoutParams
|
||||
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
val height = DensityUtil.dip2px(requireContext(), 104f)
|
||||
params.bottomMargin = height
|
||||
} else {
|
||||
val height = DensityUtil.dip2px(requireContext(), 52f)
|
||||
params.bottomMargin = height
|
||||
}
|
||||
}
|
||||
|
||||
private fun initLayoutManager() {
|
||||
layoutManager = createLayoutManager()
|
||||
}
|
||||
private fun initLayoutManager() {
|
||||
layoutManager = createLayoutManager()
|
||||
}
|
||||
|
||||
protected abstract fun createLayoutManager(): LM
|
||||
protected abstract fun createLayoutManager(): LM
|
||||
|
||||
@NonNull
|
||||
protected abstract fun createAdapter(): A
|
||||
@NonNull
|
||||
protected abstract fun createAdapter(): A
|
||||
|
||||
override fun onOffsetChanged(p0: AppBarLayout?, i: Int) {
|
||||
container.setPadding(container.paddingLeft, container.paddingTop,
|
||||
container.paddingRight, libraryFragment.totalAppBarScrollingRange + i)
|
||||
}
|
||||
override fun onOffsetChanged(p0: AppBarLayout?, i: Int) {
|
||||
container.setPadding(
|
||||
container.paddingLeft,
|
||||
container.paddingTop,
|
||||
container.paddingRight,
|
||||
libraryFragment.totalAppBarScrollingRange + i
|
||||
)
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
checkForPadding()
|
||||
}
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
checkForPadding()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
checkForPadding()
|
||||
}
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
checkForPadding()
|
||||
}
|
||||
|
||||
protected fun invalidateLayoutManager() {
|
||||
initLayoutManager()
|
||||
recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
protected fun invalidateLayoutManager() {
|
||||
initLayoutManager()
|
||||
recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
protected fun invalidateAdapter() {
|
||||
initAdapter()
|
||||
checkIsEmpty()
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
protected fun invalidateAdapter() {
|
||||
initAdapter()
|
||||
checkIsEmpty()
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
libraryFragment.removeOnAppBarOffsetChangedListener(this)
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
libraryFragment.removeOnAppBarOffsetChangedListener(this)
|
||||
}
|
||||
|
||||
fun recyclerView(): RecyclerView {
|
||||
return recyclerView
|
||||
}
|
||||
fun recyclerView(): RecyclerView {
|
||||
return recyclerView
|
||||
}
|
||||
}
|
|
@ -3,28 +3,18 @@ package code.name.monkey.retromusic.fragments.player.fit
|
|||
import android.animation.ObjectAnimator
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.view.*
|
||||
import android.view.animation.*
|
||||
import android.widget.SeekBar
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.appthemehelper.util.*
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.ripAlpha
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.helper.*
|
||||
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.*
|
||||
import kotlinx.android.synthetic.main.fragment_player_playback_controls.*
|
||||
|
||||
class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
||||
|
@ -116,7 +106,7 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
|
|||
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
|
||||
color
|
||||
} else {
|
||||
ThemeStore.accentColor(context!!).ripAlpha()
|
||||
ThemeStore.accentColor(requireContext()).ripAlpha()
|
||||
}
|
||||
|
||||
volumeFragment?.setTintable(colorFinal)
|
||||
|
|
|
@ -50,10 +50,8 @@ interface AlbumsPresenter : Presenter<AlbumsView> {
|
|||
override fun loadAlbums() {
|
||||
launch {
|
||||
when (val result = repository.allAlbums()) {
|
||||
is Result.Success -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
view?.albums(result.data)
|
||||
}
|
||||
is Result.Success -> withContext(Dispatchers.Main) {
|
||||
view?.albums(result.data)
|
||||
}
|
||||
is Result.Error -> withContext(Dispatchers.Main) { view?.showEmptyView() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package code.name.monkey.retromusic.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.util.StateSet;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
public class RippleUtils {
|
||||
public static final boolean USE_FRAMEWORK_RIPPLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
private static final int[] PRESSED_STATE_SET = {
|
||||
android.R.attr.state_pressed,
|
||||
};
|
||||
private static final int[] HOVERED_FOCUSED_STATE_SET = {
|
||||
android.R.attr.state_hovered, android.R.attr.state_focused,
|
||||
};
|
||||
private static final int[] FOCUSED_STATE_SET = {
|
||||
android.R.attr.state_focused,
|
||||
};
|
||||
private static final int[] HOVERED_STATE_SET = {
|
||||
android.R.attr.state_hovered,
|
||||
};
|
||||
|
||||
private static final int[] SELECTED_PRESSED_STATE_SET = {
|
||||
android.R.attr.state_selected, android.R.attr.state_pressed,
|
||||
};
|
||||
private static final int[] SELECTED_HOVERED_FOCUSED_STATE_SET = {
|
||||
android.R.attr.state_selected, android.R.attr.state_hovered, android.R.attr.state_focused,
|
||||
};
|
||||
private static final int[] SELECTED_FOCUSED_STATE_SET = {
|
||||
android.R.attr.state_selected, android.R.attr.state_focused,
|
||||
};
|
||||
private static final int[] SELECTED_HOVERED_STATE_SET = {
|
||||
android.R.attr.state_selected, android.R.attr.state_hovered,
|
||||
};
|
||||
private static final int[] SELECTED_STATE_SET = {
|
||||
android.R.attr.state_selected,
|
||||
};
|
||||
|
||||
private static final int[] ENABLED_PRESSED_STATE_SET = {
|
||||
android.R.attr.state_enabled, android.R.attr.state_pressed
|
||||
};
|
||||
|
||||
public static ColorStateList convertToRippleDrawableColor(@Nullable ColorStateList rippleColor) {
|
||||
if (USE_FRAMEWORK_RIPPLE) {
|
||||
int size = 2;
|
||||
|
||||
final int[][] states = new int[size][];
|
||||
final int[] colors = new int[size];
|
||||
int i = 0;
|
||||
|
||||
// Ideally we would define a different composite color for each state, but that causes the
|
||||
// ripple animation to abort prematurely.
|
||||
// So we only allow two base states: selected, and non-selected. For each base state, we only
|
||||
// base the ripple composite on its pressed state.
|
||||
|
||||
// Selected base state.
|
||||
states[i] = SELECTED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
// Non-selected base state.
|
||||
states[i] = StateSet.NOTHING;
|
||||
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
return new ColorStateList(states, colors);
|
||||
} else {
|
||||
int size = 10;
|
||||
|
||||
final int[][] states = new int[size][];
|
||||
final int[] colors = new int[size];
|
||||
int i = 0;
|
||||
|
||||
states[i] = SELECTED_PRESSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, SELECTED_PRESSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = SELECTED_HOVERED_FOCUSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_FOCUSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = SELECTED_FOCUSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, SELECTED_FOCUSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = SELECTED_HOVERED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, SELECTED_HOVERED_STATE_SET);
|
||||
i++;
|
||||
|
||||
// Checked state.
|
||||
states[i] = SELECTED_STATE_SET;
|
||||
colors[i] = Color.TRANSPARENT;
|
||||
i++;
|
||||
|
||||
states[i] = PRESSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, PRESSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = HOVERED_FOCUSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, HOVERED_FOCUSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = FOCUSED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, FOCUSED_STATE_SET);
|
||||
i++;
|
||||
|
||||
states[i] = HOVERED_STATE_SET;
|
||||
colors[i] = getColorForState(rippleColor, HOVERED_STATE_SET);
|
||||
i++;
|
||||
|
||||
// Default state.
|
||||
states[i] = StateSet.NOTHING;
|
||||
colors[i] = Color.TRANSPARENT;
|
||||
i++;
|
||||
|
||||
return new ColorStateList(states, colors);
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
private static int getColorForState(@Nullable ColorStateList rippleColor, int[] state) {
|
||||
int color;
|
||||
if (rippleColor != null) {
|
||||
color = rippleColor.getColorForState(state, rippleColor.getDefaultColor());
|
||||
} else {
|
||||
color = Color.TRANSPARENT;
|
||||
}
|
||||
return USE_FRAMEWORK_RIPPLE ? doubleAlpha(color) : color;
|
||||
}
|
||||
|
||||
/**
|
||||
* On API 21+, the framework composites a ripple color onto the display at about 50% opacity.
|
||||
* Since we are providing precise ripple colors, cancel that out by doubling the opacity here.
|
||||
*/
|
||||
@ColorInt
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private static int doubleAlpha(@ColorInt int color) {
|
||||
int alpha = Math.min(2 * Color.alpha(color), 255);
|
||||
return ColorUtils.setAlphaComponent(color, alpha);
|
||||
}
|
||||
}
|
|
@ -15,13 +15,17 @@
|
|||
package code.name.monkey.retromusic.views
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.NavigationViewUtil
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RippleUtils
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
|
||||
class BottomNavigationBarTinted @JvmOverloads constructor(
|
||||
|
@ -38,7 +42,11 @@ class BottomNavigationBarTinted @JvmOverloads constructor(
|
|||
val accentColor = ThemeStore.accentColor(context)
|
||||
NavigationViewUtil.setItemIconColors(this, ColorUtil.withAlpha(iconColor, 0.5f), accentColor)
|
||||
NavigationViewUtil.setItemTextColors(this, ColorUtil.withAlpha(iconColor, 0.5f), accentColor)
|
||||
|
||||
itemBackground = RippleDrawable(RippleUtils.convertToRippleDrawableColor(ColorStateList.valueOf(ThemeStore.accentColor(context).addAlpha())), ContextCompat.getDrawable(context, R.drawable.bottom_navigation_item_background), null)
|
||||
setOnApplyWindowInsetsListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.addAlpha(): Int {
|
||||
return ColorUtil.withAlpha(this, 0.12f)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue