move fragments to root directory

This commit is contained in:
dkanada 2020-12-13 22:12:59 +09:00
commit e0a184daa3
30 changed files with 59 additions and 59 deletions

View file

@ -0,0 +1,71 @@
package com.dkanada.gramophone.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.dkanada.gramophone.interfaces.MusicServiceEventListener;
import com.dkanada.gramophone.activities.base.AbsMusicServiceActivity;
public class AbsMusicServiceFragment extends Fragment implements MusicServiceEventListener {
private AbsMusicServiceActivity activity;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
activity = (AbsMusicServiceActivity) context;
} catch (ClassCastException e) {
throw new RuntimeException(context.getClass().getSimpleName() + " must be an instance of " + AbsMusicServiceActivity.class.getSimpleName());
}
}
@Override
public void onDetach() {
super.onDetach();
activity = null;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
activity.addMusicServiceEventListener(this);
}
@Override
public void onDestroyView() {
super.onDestroyView();
activity.removeMusicServiceEventListener(this);
}
@Override
public void onServiceConnected() {
}
@Override
public void onServiceDisconnected() {
}
@Override
public void onQueueChanged() {
}
@Override
public void onPlayMetadataChanged() {
}
@Override
public void onPlayStateChanged() {
}
@Override
public void onRepeatModeChanged() {
}
@Override
public void onShuffleModeChanged() {
}
}

View file

@ -0,0 +1,19 @@
package com.dkanada.gramophone.fragments.mainactivity;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import com.dkanada.gramophone.activities.MainActivity;
public abstract class AbsMainActivityFragment extends Fragment {
public MainActivity getMainActivity() {
return (MainActivity) getActivity();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}
}

View file

@ -0,0 +1,461 @@
package com.dkanada.gramophone.fragments.mainactivity.library;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.dkanada.gramophone.databinding.FragmentLibraryBinding;
import com.google.android.material.appbar.AppBarLayout;
import com.afollestad.materialcab.MaterialCab;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.common.ATHToolbarActivity;
import com.kabouzeid.appthemehelper.util.TabLayoutUtil;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.MusicLibraryPagerAdapter;
import com.dkanada.gramophone.dialogs.CreatePlaylistDialog;
import com.dkanada.gramophone.helper.sort.SortMethod;
import com.dkanada.gramophone.helper.sort.SortOrder;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.activities.MainActivity;
import com.dkanada.gramophone.activities.SearchActivity;
import com.dkanada.gramophone.fragments.mainactivity.AbsMainActivityFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.AbsLibraryPagerRecyclerViewCustomGridSizeFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.AlbumsFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.ArtistsFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.PlaylistsFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.SongsFragment;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.PreferenceUtil;
public class LibraryFragment extends AbsMainActivityFragment implements CabHolder, MainActivity.MainActivityFragmentCallbacks, ViewPager.OnPageChangeListener, SharedPreferences.OnSharedPreferenceChangeListener {
private FragmentLibraryBinding binding;
private MusicLibraryPagerAdapter pagerAdapter;
private MaterialCab cab;
public static LibraryFragment newInstance() {
return new LibraryFragment();
}
public LibraryFragment() {
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentLibraryBinding.inflate(inflater);
return binding.getRoot();
}
@Override
public void onDestroyView() {
PreferenceUtil.getInstance(getActivity()).unregisterOnSharedPreferenceChangedListener(this);
super.onDestroyView();
binding.pager.removeOnPageChangeListener(this);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
PreferenceUtil.getInstance(getActivity()).registerOnSharedPreferenceChangedListener(this);
getMainActivity().setStatusbarColorAuto();
getMainActivity().setNavigationbarColorAuto();
getMainActivity().setTaskDescriptionColorAuto();
setUpToolbar();
setUpViewPager();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (PreferenceUtil.CATEGORIES.equals(key)) {
Fragment current = getCurrentFragment();
pagerAdapter.setCategoryInfos(PreferenceUtil.getInstance(getActivity()).getCategories());
binding.pager.setOffscreenPageLimit(pagerAdapter.getCount() - 1);
int position = pagerAdapter.getItemPosition(current);
if (position < 0) position = 0;
binding.pager.setCurrentItem(position);
PreferenceUtil.getInstance(getContext()).setLastTab(position);
updateTabVisibility();
}
}
private void setUpToolbar() {
int primaryColor = ThemeStore.primaryColor(requireActivity());
binding.appbar.setBackgroundColor(primaryColor);
binding.toolbar.setBackgroundColor(primaryColor);
binding.toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp);
requireActivity().setTitle(R.string.app_name);
getMainActivity().setSupportActionBar(binding.toolbar);
}
private void setUpViewPager() {
pagerAdapter = new MusicLibraryPagerAdapter(requireActivity(), getChildFragmentManager());
binding.pager.setAdapter(pagerAdapter);
binding.pager.setOffscreenPageLimit(pagerAdapter.getCount() - 1);
binding.tabs.setupWithViewPager(binding.pager);
int primaryColor = ThemeStore.primaryColor(requireActivity());
int normalColor = ToolbarContentTintHelper.toolbarSubtitleColor(requireActivity(), primaryColor);
int selectedColor = ToolbarContentTintHelper.toolbarTitleColor(requireActivity(), primaryColor);
TabLayoutUtil.setTabIconColors(binding.tabs, normalColor, selectedColor);
binding.tabs.setTabTextColors(normalColor, selectedColor);
binding.tabs.setSelectedTabIndicatorColor(ThemeStore.accentColor(requireActivity()));
updateTabVisibility();
if (PreferenceUtil.getInstance(getContext()).getRememberLastTab()) {
binding.pager.setCurrentItem(PreferenceUtil.getInstance(getContext()).getLastTab());
}
binding.pager.addOnPageChangeListener(this);
}
private void updateTabVisibility() {
// hide the tab bar when only a single tab is visible
binding.tabs.setVisibility(pagerAdapter.getCount() == 1 ? View.GONE : View.VISIBLE);
}
public Fragment getCurrentFragment() {
return pagerAdapter.getFragment(binding.pager.getCurrentItem());
}
private boolean isPlaylistPage() {
return getCurrentFragment() instanceof PlaylistsFragment;
}
@NonNull
@Override
public MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(getMainActivity(), R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(requireActivity())))
.start(callback);
return cab;
}
public void addOnAppBarOffsetChangedListener(AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) {
binding.appbar.addOnOffsetChangedListener(onOffsetChangedListener);
}
public void removeOnAppBarOffsetChangedListener(AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) {
binding.appbar.removeOnOffsetChangedListener(onOffsetChangedListener);
}
public int getTotalAppBarScrollingRange() {
return binding.appbar.getTotalScrollRange();
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_main, menu);
if (isPlaylistPage()) {
menu.add(0, R.id.action_new_playlist, 0, R.string.action_new_playlist);
}
Fragment currentFragment = getCurrentFragment();
if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment && currentFragment.isAdded()) {
AbsLibraryPagerRecyclerViewCustomGridSizeFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment;
MenuItem gridSizeItem = menu.findItem(R.id.action_grid_size);
setUpGridSizeMenu(absLibraryRecyclerViewCustomGridSizeFragment, gridSizeItem.getSubMenu());
menu.findItem(R.id.action_colored_footers).setChecked(absLibraryRecyclerViewCustomGridSizeFragment.usePalette());
menu.findItem(R.id.action_colored_footers).setEnabled(absLibraryRecyclerViewCustomGridSizeFragment.canUsePalette());
// TODO the API doesn't support artist sorting
if (currentFragment instanceof ArtistsFragment) {
menu.removeItem(R.id.action_sort_method);
menu.removeItem(R.id.action_sort_order);
} else {
setUpSortMethodMenu(absLibraryRecyclerViewCustomGridSizeFragment, menu.findItem(R.id.action_sort_method).getSubMenu());
setUpSortOrderMenu(absLibraryRecyclerViewCustomGridSizeFragment, menu.findItem(R.id.action_sort_order).getSubMenu());
}
} else {
menu.removeItem(R.id.action_grid_size);
menu.removeItem(R.id.action_colored_footers);
menu.removeItem(R.id.action_sort_method);
menu.removeItem(R.id.action_sort_order);
}
Activity activity = getActivity();
if (activity == null) return;
ToolbarContentTintHelper.handleOnCreateOptionsMenu(getActivity(), binding.toolbar, menu, ATHToolbarActivity.getToolbarBackgroundColor(binding.toolbar));
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
Activity activity = getActivity();
if (activity == null) return;
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(activity, binding.toolbar);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
Fragment currentFragment = getCurrentFragment();
if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment) {
AbsLibraryPagerRecyclerViewCustomGridSizeFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment;
if (item.getItemId() == R.id.action_colored_footers) {
item.setChecked(!item.isChecked());
absLibraryRecyclerViewCustomGridSizeFragment.setAndSaveUsePalette(item.isChecked());
return true;
}
if (handleGridSizeMenuItem(absLibraryRecyclerViewCustomGridSizeFragment, item)) {
return true;
}
if (handleSortMethodMenuItem(absLibraryRecyclerViewCustomGridSizeFragment, item)) {
return true;
}
if (handleSortOrderMenuItem(absLibraryRecyclerViewCustomGridSizeFragment, item)) {
return true;
}
}
int id = item.getItemId();
switch (id) {
case R.id.action_shuffle_all:
Toast.makeText(requireActivity(), requireActivity().getResources().getString(R.string.error_unexpected), Toast.LENGTH_SHORT).show();
return true;
case R.id.action_new_playlist:
CreatePlaylistDialog.create().show(getChildFragmentManager(), "CREATE_PLAYLIST");
return true;
case R.id.action_search:
startActivity(new Intent(getActivity(), SearchActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
private void setUpGridSizeMenu(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull SubMenu gridSizeMenu) {
switch (fragment.getGridSize()) {
case 1:
gridSizeMenu.findItem(R.id.action_grid_size_1).setChecked(true);
break;
case 2:
gridSizeMenu.findItem(R.id.action_grid_size_2).setChecked(true);
break;
case 3:
gridSizeMenu.findItem(R.id.action_grid_size_3).setChecked(true);
break;
case 4:
gridSizeMenu.findItem(R.id.action_grid_size_4).setChecked(true);
break;
case 5:
gridSizeMenu.findItem(R.id.action_grid_size_5).setChecked(true);
break;
case 6:
gridSizeMenu.findItem(R.id.action_grid_size_6).setChecked(true);
break;
case 7:
gridSizeMenu.findItem(R.id.action_grid_size_7).setChecked(true);
break;
case 8:
gridSizeMenu.findItem(R.id.action_grid_size_8).setChecked(true);
break;
}
int maxGridSize = fragment.getMaxGridSize();
if (maxGridSize < 8) {
gridSizeMenu.findItem(R.id.action_grid_size_8).setVisible(false);
}
if (maxGridSize < 7) {
gridSizeMenu.findItem(R.id.action_grid_size_7).setVisible(false);
}
if (maxGridSize < 6) {
gridSizeMenu.findItem(R.id.action_grid_size_6).setVisible(false);
}
if (maxGridSize < 5) {
gridSizeMenu.findItem(R.id.action_grid_size_5).setVisible(false);
}
if (maxGridSize < 4) {
gridSizeMenu.findItem(R.id.action_grid_size_4).setVisible(false);
}
if (maxGridSize < 3) {
gridSizeMenu.findItem(R.id.action_grid_size_3).setVisible(false);
}
}
private boolean handleGridSizeMenuItem(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) {
int gridSize = 0;
switch (item.getItemId()) {
case R.id.action_grid_size_1:
gridSize = 1;
break;
case R.id.action_grid_size_2:
gridSize = 2;
break;
case R.id.action_grid_size_3:
gridSize = 3;
break;
case R.id.action_grid_size_4:
gridSize = 4;
break;
case R.id.action_grid_size_5:
gridSize = 5;
break;
case R.id.action_grid_size_6:
gridSize = 6;
break;
case R.id.action_grid_size_7:
gridSize = 7;
break;
case R.id.action_grid_size_8:
gridSize = 8;
break;
}
if (gridSize > 0) {
item.setChecked(true);
fragment.setAndSaveGridSize(gridSize);
binding.toolbar.getMenu().findItem(R.id.action_colored_footers).setEnabled(fragment.canUsePalette());
return true;
}
return false;
}
private void setUpSortMethodMenu(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull SubMenu sortMethodMenu) {
String currentSortMethod = fragment.getSortMethod();
sortMethodMenu.clear();
if (fragment instanceof AlbumsFragment) {
sortMethodMenu.add(0, R.id.action_sort_method_name, 0, R.string.sort_method_name)
.setChecked(currentSortMethod.equals(SortMethod.NAME));
sortMethodMenu.add(0, R.id.action_sort_method_artist, 1, R.string.sort_method_artist)
.setChecked(currentSortMethod.equals(SortMethod.ARTIST));
sortMethodMenu.add(0, R.id.action_sort_method_year, 2, R.string.sort_method_year)
.setChecked(currentSortMethod.equals(SortMethod.YEAR));
sortMethodMenu.add(0, R.id.action_sort_method_added, 3, R.string.sort_method_added)
.setChecked(currentSortMethod.equals(SortMethod.ADDED));
sortMethodMenu.add(0, R.id.action_sort_method_random, 4, R.string.sort_method_random)
.setChecked(currentSortMethod.equals(SortMethod.RANDOM));
} else if (fragment instanceof SongsFragment) {
sortMethodMenu.add(0, R.id.action_sort_method_name, 0, R.string.sort_method_name)
.setChecked(currentSortMethod.equals(SortMethod.NAME));
sortMethodMenu.add(0, R.id.action_sort_method_album, 1, R.string.sort_method_album)
.setChecked(currentSortMethod.equals(SortMethod.ALBUM));
sortMethodMenu.add(0, R.id.action_sort_method_artist, 2, R.string.sort_method_artist)
.setChecked(currentSortMethod.equals(SortMethod.ARTIST));
sortMethodMenu.add(0, R.id.action_sort_method_year, 3, R.string.sort_method_year)
.setChecked(currentSortMethod.equals(SortMethod.YEAR));
sortMethodMenu.add(0, R.id.action_sort_method_added, 4, R.string.sort_method_added)
.setChecked(currentSortMethod.equals(SortMethod.ADDED));
sortMethodMenu.add(0, R.id.action_sort_method_random, 5, R.string.sort_method_random)
.setChecked(currentSortMethod.equals(SortMethod.RANDOM));
}
sortMethodMenu.setGroupCheckable(0, true, true);
}
private void setUpSortOrderMenu(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull SubMenu sortOrderMenu) {
String currentSortOrder = fragment.getSortOrder();
sortOrderMenu.clear();
sortOrderMenu.add(0, R.id.action_sort_order_ascending, 0, R.string.sort_order_ascending)
.setChecked(currentSortOrder.equals(SortOrder.ASCENDING));
sortOrderMenu.add(0, R.id.action_sort_order_descending, 1, R.string.sort_order_descending)
.setChecked(currentSortOrder.equals(SortOrder.DESCENDING));
sortOrderMenu.setGroupCheckable(0, true, true);
}
private boolean handleSortMethodMenuItem(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) {
String sortMethod = null;
switch (item.getItemId()) {
case R.id.action_sort_method_name:
sortMethod = SortMethod.NAME;
break;
case R.id.action_sort_method_album:
sortMethod = SortMethod.ALBUM;
break;
case R.id.action_sort_method_artist:
sortMethod = SortMethod.ARTIST;
break;
case R.id.action_sort_method_year:
sortMethod = SortMethod.YEAR;
break;
case R.id.action_sort_method_added:
sortMethod = SortMethod.ADDED;
break;
case R.id.action_sort_method_random:
sortMethod = SortMethod.RANDOM;
break;
}
if (sortMethod != null) {
item.setChecked(true);
fragment.setAndSaveSortMethod(sortMethod);
return true;
}
return false;
}
private boolean handleSortOrderMenuItem(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) {
String sortOrder = null;
switch (item.getItemId()) {
case R.id.action_sort_order_ascending:
sortOrder = SortOrder.ASCENDING;
break;
case R.id.action_sort_order_descending:
sortOrder = SortOrder.DESCENDING;
break;
}
if (sortOrder != null) {
item.setChecked(true);
fragment.setAndSaveSortOrder(sortOrder);
return true;
}
return false;
}
@Override
public boolean handleBackPress() {
if (cab != null && cab.isActive()) {
cab.finish();
return true;
}
return false;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
PreferenceUtil.getInstance(getActivity()).setLastTab(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
}

View file

@ -0,0 +1,18 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.LibraryFragment;
public class AbsLibraryPagerFragment extends AbsMusicServiceFragment {
public LibraryFragment getLibraryFragment() {
return (LibraryFragment) getParentFragment();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
}
}

View file

@ -0,0 +1,183 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.util.Util;
public abstract class AbsLibraryPagerRecyclerViewCustomGridSizeFragment<A extends RecyclerView.Adapter, L extends RecyclerView.LayoutManager, Q> extends AbsLibraryPagerRecyclerViewFragment<A, L, Q> {
private int gridSize;
private String sortMethod;
private String sortOrder;
private boolean usePaletteInitialized;
private boolean usePalette;
private int currentLayoutRes;
public final int getGridSize() {
if (gridSize == 0) {
if (isLandscape()) {
gridSize = loadGridSizeLand();
} else {
gridSize = loadGridSize();
}
}
return gridSize;
}
public int getMaxGridSize() {
if (isLandscape()) {
return getResources().getInteger(R.integer.max_columns_land);
} else {
return getResources().getInteger(R.integer.max_columns);
}
}
public final boolean usePalette() {
if (!usePaletteInitialized) {
usePalette = loadUsePalette();
usePaletteInitialized = true;
}
return usePalette;
}
public final String getSortMethod() {
if (sortMethod == null) {
sortMethod = loadSortMethod();
}
return sortMethod;
}
public final String getSortOrder() {
if (sortOrder == null) {
sortOrder = loadSortOrder();
}
return sortOrder;
}
public void setAndSaveGridSize(final int gridSize) {
int oldLayoutRes = getItemLayoutRes();
this.gridSize = gridSize;
if (isLandscape()) {
saveGridSizeLand(gridSize);
} else {
saveGridSize(gridSize);
}
// only recreate the adapter and layout manager if the layout currentLayoutRes has changed
if (oldLayoutRes != getItemLayoutRes()) {
invalidateLayoutManager();
invalidateAdapter();
} else {
setGridSize(gridSize);
}
}
public void setAndSaveUsePalette(final boolean usePalette) {
this.usePalette = usePalette;
saveUsePalette(usePalette);
setUsePalette(usePalette);
}
public void setAndSaveSortMethod(final String sortMethod) {
this.sortMethod = sortMethod;
saveSortMethod(sortMethod);
setSortMethod(sortMethod);
invalidateAdapter();
}
public void setAndSaveSortOrder(final String sortOrder) {
this.sortOrder = sortOrder;
saveSortOrder(sortOrder);
setSortOrder(sortOrder);
invalidateAdapter();
}
public boolean canUsePalette() {
return getItemLayoutRes() == R.layout.item_grid;
}
@LayoutRes
protected int getItemLayoutRes() {
if (getGridSize() > getMaxGridSizeForList()) {
return R.layout.item_grid;
}
return R.layout.item_list;
}
protected final void notifyLayoutResChanged(@LayoutRes int res) {
this.currentLayoutRes = res;
RecyclerView recyclerView = getRecyclerView();
if (recyclerView != null) {
applyRecyclerViewPaddingForLayoutRes(recyclerView, currentLayoutRes);
}
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
applyRecyclerViewPaddingForLayoutRes(getRecyclerView(), currentLayoutRes);
}
protected void applyRecyclerViewPaddingForLayoutRes(@NonNull RecyclerView recyclerView, @LayoutRes int res) {
int padding;
if (res == R.layout.item_grid) {
padding = (int) (getResources().getDisplayMetrics().density * 2);
} else {
padding = 0;
}
recyclerView.setPadding(padding, padding, padding, padding);
}
protected abstract int loadGridSize();
protected abstract void saveGridSize(int gridColumns);
protected abstract int loadGridSizeLand();
protected abstract void saveGridSizeLand(int gridColumns);
protected abstract void saveUsePalette(boolean usePalette);
protected abstract boolean loadUsePalette();
protected abstract void setUsePalette(boolean usePalette);
protected abstract void setGridSize(int gridSize);
protected abstract String loadSortMethod();
protected abstract void saveSortMethod(String sortMethod);
protected abstract void setSortMethod(String sortMethod);
protected abstract String loadSortOrder();
protected abstract void saveSortOrder(String sortOrder);
protected abstract void setSortOrder(String sortOrder);
protected int getMaxGridSizeForList() {
if (isLandscape()) {
return requireActivity().getResources().getInteger(R.integer.default_list_columns_land);
}
return requireActivity().getResources().getInteger(R.integer.default_list_columns);
}
protected final boolean isLandscape() {
return Util.isLandscape(getResources());
}
}

View file

@ -0,0 +1,159 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.databinding.FragmentMainActivityRecyclerViewBinding;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.util.ViewUtil;
public abstract class AbsLibraryPagerRecyclerViewFragment<A extends RecyclerView.Adapter, L extends RecyclerView.LayoutManager, Q> extends AbsLibraryPagerFragment implements OnOffsetChangedListener {
private FragmentMainActivityRecyclerViewBinding binding;
private A adapter;
private L layoutManager;
private Q query;
public int size;
public boolean loading;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentMainActivityRecyclerViewBinding.inflate(getLayoutInflater(), container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getLibraryFragment().addOnAppBarOffsetChangedListener(this);
initAdapter();
initLayoutManager();
initQuery();
initRecyclerView();
loadItems(0);
}
private void initAdapter() {
adapter = createAdapter();
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
}
});
}
private void initLayoutManager() {
layoutManager = createLayoutManager();
}
private void initQuery() {
query = createQuery();
}
private void initRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), binding.recyclerView, ThemeStore.accentColor(requireActivity()));
binding.recyclerView.setLayoutManager(layoutManager);
binding.recyclerView.setAdapter(adapter);
}
protected void invalidateAdapter() {
initAdapter();
initQuery();
binding.recyclerView.setAdapter(adapter);
loadItems(0);
}
protected void invalidateLayoutManager() {
initLayoutManager();
binding.recyclerView.setLayoutManager(layoutManager);
}
protected RecyclerView getRecyclerView() {
return binding.recyclerView;
}
protected A getAdapter() {
return adapter;
}
protected L getLayoutManager() {
return layoutManager;
}
protected Q getQuery() {
return query;
}
@StringRes
protected int getEmptyMessage() {
return R.string.empty;
}
@NonNull
protected abstract A createAdapter();
@NonNull
protected abstract L createLayoutManager();
@NonNull
protected abstract Q createQuery();
protected abstract void loadItems(int index);
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
binding.container.setPadding(binding.container.getPaddingLeft(), binding.container.getPaddingTop(), binding.container.getPaddingRight(), getLibraryFragment().getTotalAppBarScrollingRange() + i);
int last = 0;
if (!loading && getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) getLayoutManager();
last = layoutManager.findLastVisibleItemPosition();
} else if (!loading && getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
last = layoutManager.findLastVisibleItemPosition();
}
int page = PreferenceUtil.getInstance(App.getInstance()).getPageSize();
int total = getAdapter().getItemCount();
if (last > total - page / 2 && total < size) {
query = createQuery();
loading = true;
loadItems(getAdapter().getItemCount());
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
getLibraryFragment().removeOnAppBarOffsetChangedListener(this);
}
private void checkIsEmpty() {
binding.empty.setText(getEmptyMessage());
binding.empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
}

View file

@ -0,0 +1,159 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.album.AlbumAdapter;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.List;
public class AlbumsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment<AlbumAdapter, GridLayoutManager, ItemQuery> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@NonNull
@Override
protected GridLayoutManager createLayoutManager() {
return new GridLayoutManager(getActivity(), getGridSize());
}
@NonNull
@Override
protected AlbumAdapter createAdapter() {
int itemLayoutRes = getItemLayoutRes();
notifyLayoutResChanged(itemLayoutRes);
List<Album> dataSet = getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet();
return new AlbumAdapter(getLibraryFragment().getMainActivity(), dataSet, itemLayoutRes, loadUsePalette(), getLibraryFragment());
}
@NonNull
@Override
protected ItemQuery createQuery() {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"MusicAlbum"});
query.setUserId(App.getApiClient().getCurrentUserId());
query.setRecursive(true);
query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
query.setStartIndex(getAdapter().getItemCount());
query.setParentId(QueryUtil.currentLibrary.getId());
QueryUtil.applySortMethod(query, PreferenceUtil.getInstance(App.getInstance()).getAlbumSortMethod());
QueryUtil.applySortOrder(query, PreferenceUtil.getInstance(App.getInstance()).getAlbumSortOrder());
return query;
}
protected void loadItems(int index) {
ItemQuery query = getQuery();
query.setStartIndex(index);
App.getApiClient().GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
if (index == 0) getAdapter().getDataSet().clear();
for (BaseItemDto itemDto : result.getItems()) {
getAdapter().getDataSet().add(new Album(itemDto));
}
size = result.getTotalRecordCount();
getAdapter().notifyDataSetChanged();
loading = false;
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
@Override
protected int getEmptyMessage() {
return R.string.no_albums;
}
@Override
protected String loadSortMethod() {
return PreferenceUtil.getInstance(getActivity()).getAlbumSortMethod();
}
@Override
protected void saveSortMethod(String sortMethod) {
PreferenceUtil.getInstance(getActivity()).setAlbumSortMethod(sortMethod);
}
@Override
protected void setSortMethod(String sortMethod) {
}
@Override
protected String loadSortOrder() {
return PreferenceUtil.getInstance(getActivity()).getAlbumSortOrder();
}
@Override
protected void saveSortOrder(String sortOrder) {
PreferenceUtil.getInstance(getActivity()).setAlbumSortOrder(sortOrder);
}
@Override
protected void setSortOrder(String sortOrder) {
}
@Override
public boolean loadUsePalette() {
return PreferenceUtil.getInstance(getActivity()).getAlbumColoredFooters();
}
@Override
protected void setUsePalette(boolean usePalette) {
getAdapter().usePalette(usePalette);
}
@Override
protected void setGridSize(int gridSize) {
getLayoutManager().setSpanCount(gridSize);
getAdapter().notifyDataSetChanged();
}
@Override
protected int loadGridSize() {
return PreferenceUtil.getInstance(getActivity()).getAlbumGridSize(requireActivity());
}
@Override
protected void saveGridSize(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setAlbumGridSize(gridSize);
}
@Override
protected int loadGridSizeLand() {
return PreferenceUtil.getInstance(getActivity()).getAlbumGridSizeLand(requireActivity());
}
@Override
protected void saveGridSizeLand(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setAlbumGridSizeLand(gridSize);
}
@Override
protected void saveUsePalette(boolean usePalette) {
PreferenceUtil.getInstance(getActivity()).setAlbumColoredFooters(usePalette);
}
}

View file

@ -0,0 +1,171 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.artist.ArtistAdapter;
import com.dkanada.gramophone.helper.sort.SortMethod;
import com.dkanada.gramophone.helper.sort.SortOrder;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ArtistsQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.List;
public class ArtistsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment<ArtistAdapter, GridLayoutManager, ArtistsQuery> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@NonNull
@Override
protected GridLayoutManager createLayoutManager() {
return new GridLayoutManager(getActivity(), getGridSize());
}
@NonNull
@Override
protected ArtistAdapter createAdapter() {
int itemLayoutRes = getItemLayoutRes();
notifyLayoutResChanged(itemLayoutRes);
List<Artist> dataSet = getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet();
return new ArtistAdapter(getLibraryFragment().getMainActivity(), dataSet, itemLayoutRes, loadUsePalette(), getLibraryFragment());
}
@NonNull
@Override
protected ArtistsQuery createQuery() {
ArtistsQuery query = new ArtistsQuery();
query.setFields(new ItemFields[]{ItemFields.Genres});
query.setUserId(App.getApiClient().getCurrentUserId());
query.setRecursive(true);
query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
query.setStartIndex(getAdapter().getItemCount());
query.setParentId(QueryUtil.currentLibrary.getId());
return query;
}
@Override
protected void loadItems(int index) {
ArtistsQuery query = getQuery();
query.setStartIndex(index);
App.getApiClient().GetAlbumArtistsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
if (index == 0) getAdapter().getDataSet().clear();
for (BaseItemDto itemDto : result.getItems()) {
getAdapter().getDataSet().add(new Artist(itemDto));
}
size = result.getTotalRecordCount();
getAdapter().notifyDataSetChanged();
loading = false;
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
@Override
protected int getEmptyMessage() {
return R.string.no_artists;
}
@LayoutRes
protected int getItemLayoutRes() {
if (getGridSize() > getMaxGridSizeForList()) {
return R.layout.item_grid;
}
return R.layout.item_list_single_row;
}
@Override
protected String loadSortMethod() {
return SortMethod.NAME;
}
@Override
protected void saveSortMethod(String sortMethod) {
// not supported through API
}
@Override
protected void setSortMethod(String sortMethod) {
}
@Override
protected String loadSortOrder() {
return SortOrder.ASCENDING;
}
@Override
protected void saveSortOrder(String sortOrder) {
// not supported through API
}
@Override
protected void setSortOrder(String sortOrder) {
}
@Override
protected int loadGridSize() {
return PreferenceUtil.getInstance(getActivity()).getArtistGridSize(requireActivity());
}
@Override
protected void saveGridSize(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setArtistGridSize(gridSize);
}
@Override
protected int loadGridSizeLand() {
return PreferenceUtil.getInstance(getActivity()).getArtistGridSizeLand(requireActivity());
}
@Override
protected void saveGridSizeLand(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setArtistGridSizeLand(gridSize);
}
@Override
protected void saveUsePalette(boolean usePalette) {
PreferenceUtil.getInstance(getActivity()).setArtistColoredFooters(usePalette);
}
@Override
public boolean loadUsePalette() {
return PreferenceUtil.getInstance(getActivity()).getArtistColoredFooters();
}
@Override
protected void setUsePalette(boolean usePalette) {
getAdapter().usePalette(usePalette);
}
@Override
protected void setGridSize(int gridSize) {
getLayoutManager().setSpanCount(gridSize);
getAdapter().notifyDataSetChanged();
}
}

View file

@ -0,0 +1,85 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.GenreAdapter;
import com.dkanada.gramophone.model.Genre;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemsByNameQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.List;
public class GenresFragment extends AbsLibraryPagerRecyclerViewFragment<GenreAdapter, LinearLayoutManager, ItemsByNameQuery> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@NonNull
@Override
protected LinearLayoutManager createLayoutManager() {
return new LinearLayoutManager(getActivity());
}
@NonNull
@Override
protected GenreAdapter createAdapter() {
List<Genre> dataSet = getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet();
return new GenreAdapter(getLibraryFragment().getMainActivity(), dataSet, R.layout.item_list_single_row);
}
@NonNull
@Override
protected ItemsByNameQuery createQuery() {
ItemsByNameQuery query = new ItemsByNameQuery();
query.setUserId(App.getApiClient().getCurrentUserId());
query.setRecursive(true);
query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
query.setStartIndex(getAdapter().getItemCount());
query.setParentId(QueryUtil.currentLibrary.getId());
return query;
}
@Override
protected void loadItems(int index) {
ItemsByNameQuery query = getQuery();
query.setStartIndex(index);
App.getApiClient().GetGenresAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
if (index == 0) getAdapter().getDataSet().clear();
for (BaseItemDto itemDto : result.getItems()) {
getAdapter().getDataSet().add(new Genre(itemDto));
}
size = result.getTotalRecordCount();
getAdapter().notifyDataSetChanged();
loading = false;
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
@Override
protected int getEmptyMessage() {
return R.string.no_genres;
}
}

View file

@ -0,0 +1,86 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.PlaylistAdapter;
import com.dkanada.gramophone.model.Playlist;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.List;
public class PlaylistsFragment extends AbsLibraryPagerRecyclerViewFragment<PlaylistAdapter, LinearLayoutManager, ItemQuery> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@NonNull
@Override
protected LinearLayoutManager createLayoutManager() {
return new LinearLayoutManager(getActivity());
}
@NonNull
@Override
protected PlaylistAdapter createAdapter() {
List<Playlist> dataSet = getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet();
return new PlaylistAdapter(getLibraryFragment().getMainActivity(), dataSet, R.layout.item_list_single_row, getLibraryFragment());
}
@NonNull
@Override
protected ItemQuery createQuery() {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"Playlist"});
query.setUserId(App.getApiClient().getCurrentUserId());
query.setRecursive(true);
query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
query.setStartIndex(getAdapter().getItemCount());
query.setParentId(QueryUtil.currentLibrary.getId());
return query;
}
@Override
protected void loadItems(int index) {
ItemQuery query = getQuery();
query.setStartIndex(index);
App.getApiClient().GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
if (index == 0) getAdapter().getDataSet().clear();
for (BaseItemDto itemDto : result.getItems()) {
getAdapter().getDataSet().add(new Playlist(itemDto));
}
size = result.getTotalRecordCount();
getAdapter().notifyDataSetChanged();
loading = false;
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
@Override
protected int getEmptyMessage() {
return R.string.no_playlists;
}
}

View file

@ -0,0 +1,182 @@
package com.dkanada.gramophone.fragments.mainactivity.library.pager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.song.ShuffleButtonSongAdapter;
import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import java.util.ArrayList;
import java.util.List;
public class SongsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment<SongAdapter, GridLayoutManager, ItemQuery> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@NonNull
@Override
protected GridLayoutManager createLayoutManager() {
return new GridLayoutManager(getActivity(), getGridSize());
}
@NonNull
@Override
protected SongAdapter createAdapter() {
int itemLayoutRes = getItemLayoutRes();
notifyLayoutResChanged(itemLayoutRes);
boolean usePalette = loadUsePalette();
List<Song> dataSet = getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet();
SongAdapter adapter;
if (getGridSize() <= getMaxGridSizeForList()) {
adapter = new ShuffleButtonSongAdapter(
getLibraryFragment().getMainActivity(),
dataSet,
itemLayoutRes,
usePalette,
getLibraryFragment());
} else {
adapter = new SongAdapter(
getLibraryFragment().getMainActivity(),
dataSet,
itemLayoutRes,
usePalette,
getLibraryFragment());
}
return adapter;
}
@NonNull
@Override
protected ItemQuery createQuery() {
ItemQuery query = new ItemQuery();
query.setIncludeItemTypes(new String[]{"Audio"});
query.setFields(new ItemFields[]{ItemFields.MediaSources});
query.setUserId(App.getApiClient().getCurrentUserId());
query.setRecursive(true);
query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
query.setStartIndex(getAdapter().getItemCount());
query.setParentId(QueryUtil.currentLibrary.getId());
QueryUtil.applySortMethod(query, PreferenceUtil.getInstance(App.getInstance()).getSongSortMethod());
QueryUtil.applySortOrder(query, PreferenceUtil.getInstance(App.getInstance()).getSongSortOrder());
return query;
}
@Override
protected void loadItems(int index) {
ItemQuery query = getQuery();
query.setStartIndex(index);
App.getApiClient().GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
if (index == 0) getAdapter().getDataSet().clear();
for (BaseItemDto itemDto : result.getItems()) {
getAdapter().getDataSet().add(new Song(itemDto));
}
size = result.getTotalRecordCount();
getAdapter().notifyDataSetChanged();
loading = false;
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
});
}
@Override
protected int getEmptyMessage() {
return R.string.no_songs;
}
@Override
protected String loadSortMethod() {
return PreferenceUtil.getInstance(getActivity()).getSongSortMethod();
}
@Override
protected void saveSortMethod(String sortMethod) {
PreferenceUtil.getInstance(getActivity()).setSongSortMethod(sortMethod);
}
@Override
protected void setSortMethod(String sortMethod) {
}
@Override
protected String loadSortOrder() {
return PreferenceUtil.getInstance(getActivity()).getSongSortOrder();
}
@Override
protected void saveSortOrder(String sortOrder) {
PreferenceUtil.getInstance(getActivity()).setSongSortOrder(sortOrder);
}
@Override
protected void setSortOrder(String sortOrder) {
}
@Override
protected int loadGridSize() {
return PreferenceUtil.getInstance(getActivity()).getSongGridSize(requireActivity());
}
@Override
protected void saveGridSize(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setSongGridSize(gridSize);
}
@Override
protected int loadGridSizeLand() {
return PreferenceUtil.getInstance(getActivity()).getSongGridSizeLand(requireActivity());
}
@Override
protected void saveGridSizeLand(int gridSize) {
PreferenceUtil.getInstance(getActivity()).setSongGridSizeLand(gridSize);
}
@Override
public void saveUsePalette(boolean usePalette) {
PreferenceUtil.getInstance(getActivity()).setSongColoredFooters(usePalette);
}
@Override
public boolean loadUsePalette() {
return PreferenceUtil.getInstance(getActivity()).getSongColoredFooters();
}
@Override
public void setUsePalette(boolean usePalette) {
getAdapter().usePalette(usePalette);
}
@Override
protected void setGridSize(int gridSize) {
getLayoutManager().setSpanCount(gridSize);
getAdapter().notifyDataSetChanged();
}
}

View file

@ -0,0 +1,148 @@
package com.dkanada.gramophone.fragments.player;
import android.content.Context;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
import com.dkanada.gramophone.dialogs.CreatePlaylistDialog;
import com.dkanada.gramophone.dialogs.SleepTimerDialog;
import com.dkanada.gramophone.dialogs.SongDetailDialog;
import com.dkanada.gramophone.dialogs.SongShareDialog;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.NavigationUtil;
public abstract class AbsPlayerFragment extends AbsMusicServiceFragment implements Toolbar.OnMenuItemClickListener, PaletteColorHolder {
private static boolean isToolbarShown = true;
private Callbacks callbacks;
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callbacks = (Callbacks) context;
} catch (ClassCastException e) {
throw new RuntimeException(context.getClass().getSimpleName() + " must implement " + Callbacks.class.getSimpleName());
}
}
@Override
public void onDetach() {
super.onDetach();
callbacks = null;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
final Song song = MusicPlayerRemote.getCurrentSong();
switch (item.getItemId()) {
case R.id.action_sleep_timer:
new SleepTimerDialog().show(getParentFragmentManager(), "SET_SLEEP_TIMER");
return true;
case R.id.action_toggle_favorite:
toggleFavorite(song);
return true;
case R.id.action_share:
SongShareDialog.create(song).show(getParentFragmentManager(), "SHARE_SONG");
return true;
case R.id.action_add_to_playlist:
AddToPlaylistDialog.create(song).show(getParentFragmentManager(), "ADD_PLAYLIST");
return true;
case R.id.action_clear_queue:
MusicPlayerRemote.clearQueue();
return true;
case R.id.action_save_queue:
CreatePlaylistDialog.create(MusicPlayerRemote.getPlayingQueue()).show(getParentFragmentManager(), "ADD_TO_PLAYLIST");
return true;
case R.id.action_details:
SongDetailDialog.create(song).show(getParentFragmentManager(), "SONG_DETAIL");
return true;
case R.id.action_go_to_album:
NavigationUtil.goToAlbum(requireActivity(), new Album(song));
return true;
case R.id.action_go_to_artist:
NavigationUtil.goToArtist(requireActivity(), new Artist(song));
return true;
}
return false;
}
protected void toggleFavorite(Song song) {
MusicUtil.toggleFavorite(requireActivity(), song);
}
protected boolean isToolbarShown() {
return isToolbarShown;
}
protected void setToolbarShown(boolean toolbarShown) {
isToolbarShown = toolbarShown;
}
protected void showToolbar(@Nullable final View toolbar) {
if (toolbar == null) return;
setToolbarShown(true);
toolbar.setVisibility(View.VISIBLE);
toolbar.animate().alpha(1f).setDuration(PlayerAlbumCoverFragment.VISIBILITY_ANIM_DURATION);
}
protected void hideToolbar(@Nullable final View toolbar) {
if (toolbar == null) return;
setToolbarShown(false);
toolbar.animate().alpha(0f).setDuration(PlayerAlbumCoverFragment.VISIBILITY_ANIM_DURATION).withEndAction(() -> toolbar.setVisibility(View.GONE));
}
protected void toggleToolbar(@Nullable final View toolbar) {
if (isToolbarShown()) {
hideToolbar(toolbar);
} else {
showToolbar(toolbar);
}
}
protected void checkToggleToolbar(@Nullable final View toolbar) {
if (toolbar != null && !isToolbarShown() && toolbar.getVisibility() != View.GONE) {
hideToolbar(toolbar);
} else if (toolbar != null && isToolbarShown() && toolbar.getVisibility() != View.VISIBLE) {
showToolbar(toolbar);
}
}
protected String getUpNextAndQueueTime() {
final long duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.getPosition());
return MusicUtil.buildInfoString(
getResources().getString(R.string.up_next),
MusicUtil.getReadableDurationString(duration)
);
}
public abstract void onShow();
public abstract void onHide();
public abstract boolean onBackPressed();
public Callbacks getCallbacks() {
return callbacks;
}
public interface Callbacks {
void onPaletteColorChanged();
}
}

View file

@ -0,0 +1,142 @@
package com.dkanada.gramophone.fragments.player;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.dkanada.gramophone.databinding.FragmentMiniPlayerBinding;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.helper.MusicProgressViewUpdateHelper;
import com.dkanada.gramophone.helper.PlayPauseButtonOnClickHandler;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.views.PlayPauseDrawable;
public class MiniPlayerFragment extends AbsMusicServiceFragment implements MusicProgressViewUpdateHelper.Callback {
private FragmentMiniPlayerBinding binding;
private PlayPauseDrawable miniPlayerPlayPauseDrawable;
private MusicProgressViewUpdateHelper progressViewUpdateHelper;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentMiniPlayerBinding.inflate(inflater);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setOnTouchListener(new FlingPlayBackController(getActivity()));
setUpMiniPlayer();
}
private void setUpMiniPlayer() {
setUpPlayPauseButton();
binding.progressBar.setProgressTintList(ColorStateList.valueOf(ThemeStore.accentColor(requireActivity())));
}
private void setUpPlayPauseButton() {
miniPlayerPlayPauseDrawable = new PlayPauseDrawable(requireActivity());
binding.miniPlayerPlayPauseButton.setImageDrawable(miniPlayerPlayPauseDrawable);
binding.miniPlayerPlayPauseButton.setColorFilter(ATHUtil.resolveColor(requireActivity(), R.attr.iconColor, ThemeStore.textColorSecondary(requireActivity())), PorterDuff.Mode.SRC_IN);
binding.miniPlayerPlayPauseButton.setOnClickListener(new PlayPauseButtonOnClickHandler());
}
private void updateSongTitle() {
binding.miniPlayerTitle.setText(MusicPlayerRemote.getCurrentSong().title);
}
@Override
public void onServiceConnected() {
updateSongTitle();
updatePlayPauseDrawableState(false);
}
@Override
public void onPlayMetadataChanged() {
updateSongTitle();
}
@Override
public void onPlayStateChanged() {
updatePlayPauseDrawableState(true);
}
@Override
public void onUpdateProgressViews(int progress, int total) {
binding.progressBar.setMax(total);
binding.progressBar.setProgress(progress);
}
@Override
public void onResume() {
super.onResume();
progressViewUpdateHelper.start();
}
@Override
public void onPause() {
super.onPause();
progressViewUpdateHelper.stop();
}
private static class FlingPlayBackController implements View.OnTouchListener {
GestureDetector flingPlayBackController;
public FlingPlayBackController(Context context) {
flingPlayBackController = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
if (velocityX < 0) {
MusicPlayerRemote.playNextSong();
return true;
} else if (velocityX > 0) {
MusicPlayerRemote.playPreviousSong();
return true;
}
}
return false;
}
});
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(View v, MotionEvent event) {
return flingPlayBackController.onTouchEvent(event);
}
}
protected void updatePlayPauseDrawableState(boolean animate) {
if (MusicPlayerRemote.isPlaying()) {
miniPlayerPlayPauseDrawable.setPause(animate);
} else {
miniPlayerPlayPauseDrawable.setPlay(animate);
}
}
}

View file

@ -0,0 +1,25 @@
package com.dkanada.gramophone.fragments.player;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import com.dkanada.gramophone.R;
public enum NowPlayingScreen {
CARD(R.string.card, R.drawable.np_card, 0),
FLAT(R.string.flat, R.drawable.np_flat, 1);
@StringRes
public final int titleRes;
@DrawableRes
public final int drawableRes;
public final int id;
NowPlayingScreen(@StringRes int titleRes, @DrawableRes int drawableRes, int id) {
this.titleRes = titleRes;
this.drawableRes = drawableRes;
this.id = id;
}
}

View file

@ -0,0 +1,165 @@
package com.dkanada.gramophone.fragments.player;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.ViewPager;
import com.dkanada.gramophone.adapter.AlbumCoverPagerAdapter;
import com.dkanada.gramophone.databinding.FragmentPlayerAlbumCoverBinding;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.misc.SimpleAnimatorListener;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.util.ViewUtil;
public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements ViewPager.OnPageChangeListener {
public static final int VISIBILITY_ANIM_DURATION = 300;
private FragmentPlayerAlbumCoverBinding binding;
private Callbacks callbacks;
private int currentPosition;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentPlayerAlbumCoverBinding.inflate(inflater);
return binding.getRoot();
}
@Override
@SuppressLint("ClickableViewAccessibility")
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.playerAlbumCoverViewPager.addOnPageChangeListener(this);
binding.playerAlbumCoverViewPager.setOnTouchListener(new View.OnTouchListener() {
GestureDetector gestureDetector = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (callbacks != null) {
callbacks.onToolbarToggled();
return true;
}
return super.onSingleTapConfirmed(e);
}
});
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding.playerAlbumCoverViewPager.removeOnPageChangeListener(this);
}
@Override
public void onServiceConnected() {
updatePlayingQueue();
}
@Override
public void onPlayMetadataChanged() {
binding.playerAlbumCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition());
}
@Override
public void onQueueChanged() {
updatePlayingQueue();
}
private void updatePlayingQueue() {
binding.playerAlbumCoverViewPager.setAdapter(new AlbumCoverPagerAdapter(getFragmentManager(), MusicPlayerRemote.getPlayingQueue()));
binding.playerAlbumCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition());
onPageSelected(MusicPlayerRemote.getPosition());
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
currentPosition = position;
((AlbumCoverPagerAdapter) binding.playerAlbumCoverViewPager.getAdapter()).receiveColor(colorReceiver, position);
if (position != MusicPlayerRemote.getPosition()) {
MusicPlayerRemote.playSongAt(position);
}
}
private AlbumCoverPagerAdapter.AlbumCoverFragment.ColorReceiver colorReceiver = new AlbumCoverPagerAdapter.AlbumCoverFragment.ColorReceiver() {
@Override
public void onColorReady(int color, int requestCode) {
if (currentPosition == requestCode) {
notifyColorChange(color);
}
}
};
@Override
public void onPageScrollStateChanged(int state) {
}
public void showHeartAnimation() {
binding.playerFavoriteIcon.clearAnimation();
binding.playerFavoriteIcon.setAlpha(0f);
binding.playerFavoriteIcon.setScaleX(0f);
binding.playerFavoriteIcon.setScaleY(0f);
binding.playerFavoriteIcon.setVisibility(View.VISIBLE);
binding.playerFavoriteIcon.setPivotX(binding.playerFavoriteIcon.getWidth() / 2);
binding.playerFavoriteIcon.setPivotY(binding.playerFavoriteIcon.getHeight() / 2);
binding.playerFavoriteIcon.animate()
.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME / 2)
.setInterpolator(new DecelerateInterpolator())
.scaleX(1f)
.scaleY(1f)
.alpha(1f)
.setListener(new SimpleAnimatorListener() {
@Override
public void onAnimationCancel(Animator animation) {
binding.playerFavoriteIcon.setVisibility(View.INVISIBLE);
}
})
.withEndAction(() -> binding.playerFavoriteIcon.animate()
.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME / 2)
.setInterpolator(new AccelerateInterpolator())
.scaleX(0f)
.scaleY(0f)
.alpha(0f)
.start())
.start();
}
private void notifyColorChange(int color) {
if (callbacks != null) callbacks.onColorChanged(color);
}
public void setCallbacks(Callbacks listener) {
callbacks = listener;
}
public interface Callbacks {
void onColorChanged(int color);
void onFavoriteToggled();
void onToolbarToggled();
}
}

View file

@ -0,0 +1,497 @@
package com.dkanada.gramophone.fragments.player.card;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.dkanada.gramophone.databinding.FragmentCardPlayerBinding;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
import com.dkanada.gramophone.adapter.song.PlayingQueueAdapter;
import com.dkanada.gramophone.dialogs.SongShareDialog;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.helper.menu.SongMenuHelper;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.fragments.player.AbsPlayerFragment;
import com.dkanada.gramophone.fragments.player.PlayerAlbumCoverFragment;
import com.dkanada.gramophone.util.ImageUtil;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.Util;
import com.dkanada.gramophone.util.ViewUtil;
import com.dkanada.gramophone.views.WidthFitSquareLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks, SlidingUpPanelLayout.PanelSlideListener {
private FragmentCardPlayerBinding binding;
private int lastColor;
private CardPlayerPlaybackControlsFragment playbackControlsFragment;
private PlayerAlbumCoverFragment playerAlbumCoverFragment;
private LinearLayoutManager layoutManager;
private PlayingQueueAdapter playingQueueAdapter;
private RecyclerView.Adapter wrappedAdapter;
private RecyclerViewDragDropManager recyclerViewDragDropManager;
private Impl impl;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentCardPlayerBinding.inflate(inflater);
if (Util.isLandscape(getResources())) {
impl = new LandscapeImpl(this, binding);
} else {
impl = new PortraitImpl(this, binding);
}
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
impl.init();
setUpPlayerToolbar();
setUpSubFragments();
setUpRecyclerView();
binding.playerSlidingLayout.addPanelSlideListener(this);
binding.playerSlidingLayout.setAntiDragView(view.findViewById(R.id.draggable_area));
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
impl.setUpPanelAndAlbumCoverHeight();
}
});
// for some reason the xml attribute doesn't get applied here.
binding.playingQueueCard.setCardBackgroundColor(ATHUtil.resolveColor(getActivity(), R.attr.cardBackgroundColor));
}
@Override
public void onDestroyView() {
binding.playerSlidingLayout.removePanelSlideListener(this);
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.release();
recyclerViewDragDropManager = null;
}
binding.playerRecyclerView.setItemAnimator(null);
binding.playerRecyclerView.setAdapter(null);
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
playingQueueAdapter = null;
layoutManager = null;
super.onDestroyView();
}
@Override
public void onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.cancelDrag();
}
super.onPause();
}
@Override
public void onResume() {
super.onResume();
checkToggleToolbar(binding.toolbarContainer);
}
@Override
public void onServiceConnected() {
updateQueue();
updateCurrentSong();
updateIsFavorite();
}
@Override
public void onPlayMetadataChanged() {
updateCurrentSong();
updateIsFavorite();
updateQueuePosition();
}
@Override
public void onQueueChanged() {
updateQueue();
}
private void updateQueue() {
playingQueueAdapter.swapDataSet(MusicPlayerRemote.getPlayingQueue(), MusicPlayerRemote.getPosition());
binding.playerQueueSubHeader.setText(getUpNextAndQueueTime());
if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
private void updateQueuePosition() {
playingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition());
binding.playerQueueSubHeader.setText(getUpNextAndQueueTime());
if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
private void updateCurrentSong() {
impl.updateCurrentSong(MusicPlayerRemote.getCurrentSong());
}
private void setUpSubFragments() {
playbackControlsFragment = (CardPlayerPlaybackControlsFragment) getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment);
playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment);
playerAlbumCoverFragment.setCallbacks(this);
}
private void setUpPlayerToolbar() {
binding.playerToolbar.inflateMenu(R.menu.menu_player);
binding.playerToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
binding.playerToolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed());
binding.playerToolbar.setOnMenuItemClickListener(this);
}
private void setUpRecyclerView() {
recyclerViewDragDropManager = new RecyclerViewDragDropManager();
final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator();
playingQueueAdapter = new PlayingQueueAdapter(
((AppCompatActivity) getActivity()),
MusicPlayerRemote.getPlayingQueue(),
MusicPlayerRemote.getPosition(),
R.layout.item_list,
false,
null);
wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(playingQueueAdapter);
layoutManager = new LinearLayoutManager(getActivity());
binding.playerRecyclerView.setLayoutManager(layoutManager);
binding.playerRecyclerView.setAdapter(wrappedAdapter);
binding.playerRecyclerView.setItemAnimator(animator);
recyclerViewDragDropManager.attachRecyclerView(binding.playerRecyclerView);
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
private void updateIsFavorite() {
boolean favorite = MusicPlayerRemote.getCurrentSong().favorite;
int res = favorite ? R.drawable.ic_favorite_white_24dp : R.drawable.ic_favorite_border_white_24dp;
int color = ToolbarContentTintHelper.toolbarContentColor(getActivity(), Color.TRANSPARENT);
Drawable drawable = ImageUtil.getTintedVectorDrawable(getActivity(), res, color);
binding.playerToolbar.getMenu().findItem(R.id.action_toggle_favorite)
.setIcon(drawable)
.setTitle(favorite ? getString(R.string.action_remove_from_favorites) : getString(R.string.action_add_to_favorites));
}
@Override
@ColorInt
public int getPaletteColor() {
return lastColor;
}
private void animateColorChange(final int newColor) {
impl.animateColorChange(newColor);
lastColor = newColor;
}
@Override
protected void toggleFavorite(Song song) {
super.toggleFavorite(song);
if (song.id.equals(MusicPlayerRemote.getCurrentSong().id)) {
if (song.favorite) {
playerAlbumCoverFragment.showHeartAnimation();
}
updateIsFavorite();
}
}
@Override
public void onShow() {
playbackControlsFragment.show();
}
@Override
public void onHide() {
playbackControlsFragment.hide();
onBackPressed();
}
@Override
public boolean onBackPressed() {
boolean wasExpanded = binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED;
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
return wasExpanded;
}
@Override
public void onColorChanged(int color) {
animateColorChange(color);
playbackControlsFragment.setDark(ColorUtil.isColorLight(color));
getCallbacks().onPaletteColorChanged();
}
@Override
public void onFavoriteToggled() {
toggleFavorite(MusicPlayerRemote.getCurrentSong());
}
@Override
public void onToolbarToggled() {
toggleToolbar(binding.toolbarContainer);
}
@Override
public void onPanelSlide(View view, float slide) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float density = getResources().getDisplayMetrics().density;
binding.playingQueueCard.setCardElevation((6 * slide + 2) * density);
playbackControlsFragment.binding.playerPlayPauseFab.setElevation((2 * Math.max(0, (1 - (slide * 16))) + 2) * density);
}
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
switch (newState) {
case COLLAPSED:
onPanelCollapsed(panel);
break;
case ANCHORED:
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); // this fixes a bug where the panel would get stuck for some reason
break;
}
}
public void onPanelCollapsed(View panel) {
resetToCurrentPosition();
}
private void resetToCurrentPosition() {
binding.playerRecyclerView.stopScroll();
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
interface Impl {
void init();
void updateCurrentSong(Song song);
void animateColorChange(final int newColor);
void setUpPanelAndAlbumCoverHeight();
}
private static abstract class BaseImpl implements Impl {
protected CardPlayerFragment fragment;
protected FragmentCardPlayerBinding binding;
public BaseImpl(CardPlayerFragment fragment, FragmentCardPlayerBinding binding) {
this.fragment = fragment;
this.binding = binding;
}
public AnimatorSet createDefaultColorChangeAnimatorSet(int newColor) {
Animator backgroundAnimator;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// noinspection ConstantConditions
int x = (int) (fragment.playbackControlsFragment.binding.playerPlayPauseFab.getX() + fragment.playbackControlsFragment.binding.playerPlayPauseFab.getWidth() / 2 + fragment.playbackControlsFragment.getView().getX());
int y = (int) (fragment.playbackControlsFragment.binding.playerPlayPauseFab.getY() + fragment.playbackControlsFragment.binding.playerPlayPauseFab.getHeight() / 2 + fragment.playbackControlsFragment.getView().getY() + fragment.playbackControlsFragment.binding.playerProgressSlider.getHeight());
float startRadius = Math.max(fragment.playbackControlsFragment.binding.playerPlayPauseFab.getWidth() / 2, fragment.playbackControlsFragment.binding.playerPlayPauseFab.getHeight() / 2);
float endRadius = Math.max(binding.colorBackground.getWidth(), binding.colorBackground.getHeight());
binding.colorBackground.setBackgroundColor(newColor);
backgroundAnimator = ViewAnimationUtils.createCircularReveal(binding.colorBackground, x, y, startRadius, endRadius);
} else {
backgroundAnimator = ViewUtil.createBackgroundColorTransition(binding.colorBackground, fragment.lastColor, newColor);
}
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(backgroundAnimator);
if (!ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
int adjustedLastColor = ColorUtil.isColorLight(fragment.lastColor) ? ColorUtil.darkenColor(fragment.lastColor) : fragment.lastColor;
int adjustedNewColor = ColorUtil.isColorLight(newColor) ? ColorUtil.darkenColor(newColor) : newColor;
Animator subHeaderAnimator = ViewUtil.createTextColorTransition(binding.playerQueueSubHeader, adjustedLastColor, adjustedNewColor);
animatorSet.play(subHeaderAnimator);
}
animatorSet.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME);
return animatorSet;
}
@Override
public void animateColorChange(int newColor) {
if (ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
binding.playerQueueSubHeader.setTextColor(ThemeStore.textColorSecondary(fragment.getActivity()));
}
}
}
@SuppressWarnings("ConstantConditions")
private static class PortraitImpl extends BaseImpl {
MediaEntryViewHolder currentSongViewHolder;
Song currentSong = Song.EMPTY;
public PortraitImpl(CardPlayerFragment fragment, FragmentCardPlayerBinding binding) {
super(fragment, binding);
}
@Override
public void init() {
currentSongViewHolder = new MediaEntryViewHolder(fragment.getView().findViewById(R.id.current_song));
currentSongViewHolder.separator.setVisibility(View.VISIBLE);
currentSongViewHolder.shortSeparator.setVisibility(View.GONE);
currentSongViewHolder.image.setScaleType(ImageView.ScaleType.CENTER);
currentSongViewHolder.image.setColorFilter(ATHUtil.resolveColor(fragment.getActivity(), R.attr.iconColor, ThemeStore.textColorSecondary(fragment.getActivity())), PorterDuff.Mode.SRC_IN);
currentSongViewHolder.image.setImageResource(R.drawable.ic_volume_up_white_24dp);
currentSongViewHolder.itemView.setOnClickListener(v -> {
// toggle the panel
if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
} else if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
});
currentSongViewHolder.menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu((AppCompatActivity) fragment.getActivity()) {
@Override
public Song getSong() {
return currentSong;
}
public int getMenuRes() {
return R.menu.menu_item_playing_queue_song;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_remove_from_queue:
MusicPlayerRemote.removeFromQueue(MusicPlayerRemote.getPosition());
return true;
case R.id.action_share:
SongShareDialog.create(getSong()).show(fragment.getFragmentManager(), "SONG_SHARE_DIALOG");
return true;
}
return super.onMenuItemClick(item);
}
});
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
WidthFitSquareLayout albumCoverContainer = fragment.getView().findViewById(R.id.album_cover_container);
final int availablePanelHeight = binding.playerSlidingLayout.getHeight() - fragment.getView().findViewById(R.id.player_content).getHeight() + (int) ViewUtil.convertDpToPixel(8, fragment.getResources());
final int minPanelHeight = (int) ViewUtil.convertDpToPixel(72 + 24, fragment.getResources());
if (availablePanelHeight < minPanelHeight) {
albumCoverContainer.getLayoutParams().height = albumCoverContainer.getHeight() - (minPanelHeight - availablePanelHeight);
albumCoverContainer.forceSquare(false);
}
binding.playerSlidingLayout.setPanelHeight(Math.max(minPanelHeight, availablePanelHeight));
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(binding.playerSlidingLayout.findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
currentSong = song;
currentSongViewHolder.title.setText(song.title);
currentSongViewHolder.text.setText(MusicUtil.getSongInfoString(song));
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
binding.playerSlidingLayout.setBackgroundColor(fragment.lastColor);
createDefaultColorChangeAnimatorSet(newColor).start();
}
}
@SuppressWarnings("ConstantConditions")
private static class LandscapeImpl extends BaseImpl {
public LandscapeImpl(CardPlayerFragment fragment, FragmentCardPlayerBinding binding) {
super(fragment, binding);
}
@Override
public void init() {
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
int panelHeight = binding.playerSlidingLayout.getHeight() - fragment.playbackControlsFragment.getView().getHeight();
binding.playerSlidingLayout.setPanelHeight(panelHeight);
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(binding.playerSlidingLayout.findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
binding.playerToolbar.setTitle(song.title);
binding.playerToolbar.setSubtitle(MusicUtil.getSongInfoString(song));
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
binding.playerSlidingLayout.setBackgroundColor(fragment.lastColor);
AnimatorSet animatorSet = createDefaultColorChangeAnimatorSet(newColor);
animatorSet.play(ViewUtil.createBackgroundColorTransition(binding.playerToolbar, fragment.lastColor, newColor))
.with(ViewUtil.createBackgroundColorTransition(fragment.getView().findViewById(R.id.status_bar), ColorUtil.darkenColor(fragment.lastColor), ColorUtil.darkenColor(newColor)));
animatorSet.start();
}
}
}

View file

@ -0,0 +1,237 @@
package com.dkanada.gramophone.fragments.player.card;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.kabouzeid.appthemehelper.util.TintHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.databinding.FragmentCardPlayerPlaybackControlsBinding;
import com.dkanada.gramophone.helper.MusicProgressViewUpdateHelper;
import com.dkanada.gramophone.helper.PlayPauseButtonOnClickHandler;
import com.dkanada.gramophone.misc.SimpleOnSeekbarChangeListener;
import com.dkanada.gramophone.service.MusicService;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.views.PlayPauseDrawable;
public class CardPlayerPlaybackControlsFragment extends AbsMusicServiceFragment implements MusicProgressViewUpdateHelper.Callback {
public FragmentCardPlayerPlaybackControlsBinding binding;
private PlayPauseDrawable playerFabPlayPauseDrawable;
private int lastPlaybackControlsColor;
private int lastDisabledPlaybackControlsColor;
private MusicProgressViewUpdateHelper progressViewUpdateHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentCardPlayerPlaybackControlsBinding.inflate(inflater);
return binding.getRoot();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setUpMusicControllers();
updateProgressTextColor();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onResume() {
super.onResume();
progressViewUpdateHelper.start();
}
@Override
public void onPause() {
super.onPause();
progressViewUpdateHelper.stop();
}
@Override
public void onServiceConnected() {
updatePlayPauseDrawableState(false);
updateRepeatState();
updateShuffleState();
}
@Override
public void onPlayStateChanged() {
updatePlayPauseDrawableState(true);
}
@Override
public void onRepeatModeChanged() {
updateRepeatState();
}
@Override
public void onShuffleModeChanged() {
updateShuffleState();
}
public void setDark(boolean dark) {
if (dark) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true);
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true);
} else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false);
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false);
}
updateRepeatState();
updateShuffleState();
updatePrevNextColor();
updateProgressTextColor();
}
private void setUpPlayPauseFab() {
final int fabColor = Color.WHITE;
TintHelper.setTintAuto(binding.playerPlayPauseFab, fabColor, true);
playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity());
binding.playerPlayPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called
binding.playerPlayPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN);
binding.playerPlayPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler());
binding.playerPlayPauseFab.post(() -> {
if (binding.playerPlayPauseFab != null) {
binding.playerPlayPauseFab.setPivotX(binding.playerPlayPauseFab.getWidth() / 2);
binding.playerPlayPauseFab.setPivotY(binding.playerPlayPauseFab.getHeight() / 2);
}
});
}
protected void updatePlayPauseDrawableState(boolean animate) {
if (MusicPlayerRemote.isPlaying()) {
playerFabPlayPauseDrawable.setPause(animate);
} else {
playerFabPlayPauseDrawable.setPlay(animate);
}
}
private void setUpMusicControllers() {
setUpPlayPauseFab();
setUpPrevNext();
setUpRepeatButton();
setUpShuffleButton();
setUpProgressSlider();
}
private void setUpPrevNext() {
updatePrevNextColor();
binding.playerNextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong());
binding.playerPrevButton.setOnClickListener(v -> MusicPlayerRemote.back());
}
private void updateProgressTextColor() {
int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false);
binding.playerSongTotalTime.setTextColor(color);
binding.playerSongCurrentProgress.setTextColor(color);
}
private void updatePrevNextColor() {
binding.playerNextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
binding.playerPrevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
}
private void setUpShuffleButton() {
binding.playerShuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode());
}
private void updateShuffleState() {
switch (MusicPlayerRemote.getShuffleMode()) {
case MusicService.SHUFFLE_MODE_SHUFFLE:
binding.playerShuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
default:
binding.playerShuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
}
}
private void setUpRepeatButton() {
binding.playerRepeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode());
}
private void updateRepeatState() {
switch (MusicPlayerRemote.getRepeatMode()) {
case MusicService.REPEAT_MODE_NONE:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp);
binding.playerRepeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
case MusicService.REPEAT_MODE_ALL:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp);
binding.playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
case MusicService.REPEAT_MODE_THIS:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp);
binding.playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
}
}
public void show() {
binding.playerPlayPauseFab.animate()
.scaleX(1f)
.scaleY(1f)
.rotation(360f)
.setInterpolator(new DecelerateInterpolator())
.start();
}
public void hide() {
binding.playerPlayPauseFab.setScaleX(0f);
binding.playerPlayPauseFab.setScaleY(0f);
binding.playerPlayPauseFab.setRotation(0f);
}
private void setUpProgressSlider() {
int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false);
binding.playerProgressSlider.getThumb().mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);
binding.playerProgressSlider.getProgressDrawable().mutate().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN);
binding.playerProgressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress);
onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis());
}
}
});
}
@Override
public void onUpdateProgressViews(int progress, int total) {
binding.playerProgressSlider.setMax(total);
binding.playerProgressSlider.setProgress(progress);
binding.playerSongTotalTime.setText(MusicUtil.getReadableDurationString(total));
binding.playerSongCurrentProgress.setText(MusicUtil.getReadableDurationString(progress));
}
}

View file

@ -0,0 +1,475 @@
package com.dkanada.gramophone.fragments.player.flat;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.dkanada.gramophone.databinding.FragmentFlatPlayerBinding;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
import com.dkanada.gramophone.adapter.song.PlayingQueueAdapter;
import com.dkanada.gramophone.dialogs.SongShareDialog;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.helper.menu.SongMenuHelper;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.fragments.player.AbsPlayerFragment;
import com.dkanada.gramophone.fragments.player.PlayerAlbumCoverFragment;
import com.dkanada.gramophone.util.ImageUtil;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.Util;
import com.dkanada.gramophone.util.ViewUtil;
import com.dkanada.gramophone.views.WidthFitSquareLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks, SlidingUpPanelLayout.PanelSlideListener {
private FragmentFlatPlayerBinding binding;
private int lastColor;
private FlatPlayerPlaybackControlsFragment playbackControlsFragment;
private PlayerAlbumCoverFragment playerAlbumCoverFragment;
private LinearLayoutManager layoutManager;
private PlayingQueueAdapter playingQueueAdapter;
private RecyclerView.Adapter wrappedAdapter;
private RecyclerViewDragDropManager recyclerViewDragDropManager;
private Impl impl;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentFlatPlayerBinding.inflate(inflater);
if (Util.isLandscape(getResources())) {
impl = new LandscapeImpl(this, binding);
} else {
impl = new PortraitImpl(this, binding);
}
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
impl.init();
setUpPlayerToolbar();
setUpSubFragments();
setUpRecyclerView();
if (binding.playerSlidingLayout != null) {
binding.playerSlidingLayout.addPanelSlideListener(this);
binding.playerSlidingLayout.setAntiDragView(view.findViewById(R.id.draggable_area));
}
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
impl.setUpPanelAndAlbumCoverHeight();
}
});
}
@Override
public void onDestroyView() {
if (binding.playerSlidingLayout != null) {
binding.playerSlidingLayout.removePanelSlideListener(this);
}
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.release();
recyclerViewDragDropManager = null;
}
binding.playerRecyclerView.setItemAnimator(null);
binding.playerRecyclerView.setAdapter(null);
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
playingQueueAdapter = null;
layoutManager = null;
super.onDestroyView();
}
@Override
public void onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.cancelDrag();
}
super.onPause();
}
@Override
public void onResume() {
super.onResume();
checkToggleToolbar(binding.toolbarContainer);
}
@Override
public void onServiceConnected() {
updateQueue();
updateCurrentSong();
updateIsFavorite();
}
@Override
public void onPlayMetadataChanged() {
updateCurrentSong();
updateIsFavorite();
updateQueuePosition();
}
@Override
public void onQueueChanged() {
updateQueue();
}
private void updateQueue() {
playingQueueAdapter.swapDataSet(MusicPlayerRemote.getPlayingQueue(), MusicPlayerRemote.getPosition());
binding.playerQueueSubHeader.setText(getUpNextAndQueueTime());
if (binding.playerSlidingLayout == null || binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
private void updateQueuePosition() {
playingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition());
binding.playerQueueSubHeader.setText(getUpNextAndQueueTime());
if (binding.playerSlidingLayout == null || binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
private void updateCurrentSong() {
impl.updateCurrentSong(MusicPlayerRemote.getCurrentSong());
}
private void setUpSubFragments() {
playbackControlsFragment = (FlatPlayerPlaybackControlsFragment) getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment);
playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment);
playerAlbumCoverFragment.setCallbacks(this);
}
private void setUpPlayerToolbar() {
binding.playerToolbar.inflateMenu(R.menu.menu_player);
binding.playerToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
binding.playerToolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed());
binding.playerToolbar.setOnMenuItemClickListener(this);
}
private void setUpRecyclerView() {
recyclerViewDragDropManager = new RecyclerViewDragDropManager();
final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator();
playingQueueAdapter = new PlayingQueueAdapter(
((AppCompatActivity) getActivity()),
MusicPlayerRemote.getPlayingQueue(),
MusicPlayerRemote.getPosition(),
R.layout.item_list,
false,
null);
wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(playingQueueAdapter);
layoutManager = new LinearLayoutManager(getActivity());
binding.playerRecyclerView.setLayoutManager(layoutManager);
binding.playerRecyclerView.setAdapter(wrappedAdapter);
binding.playerRecyclerView.setItemAnimator(animator);
recyclerViewDragDropManager.attachRecyclerView(binding.playerRecyclerView);
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
private void updateIsFavorite() {
boolean favorite = MusicPlayerRemote.getCurrentSong().favorite;
int res = favorite ? R.drawable.ic_favorite_white_24dp : R.drawable.ic_favorite_border_white_24dp;
int color = ToolbarContentTintHelper.toolbarContentColor(getActivity(), Color.TRANSPARENT);
Drawable drawable = ImageUtil.getTintedVectorDrawable(getActivity(), res, color);
binding.playerToolbar.getMenu().findItem(R.id.action_toggle_favorite)
.setIcon(drawable)
.setTitle(favorite ? getString(R.string.action_remove_from_favorites) : getString(R.string.action_add_to_favorites));
}
@Override
@ColorInt
public int getPaletteColor() {
return lastColor;
}
private void animateColorChange(final int newColor) {
impl.animateColorChange(newColor);
lastColor = newColor;
}
@Override
protected void toggleFavorite(Song song) {
super.toggleFavorite(song);
if (song.id.equals(MusicPlayerRemote.getCurrentSong().id)) {
if (song.favorite) {
playerAlbumCoverFragment.showHeartAnimation();
}
updateIsFavorite();
}
}
@Override
public void onShow() {
playbackControlsFragment.show();
}
@Override
public void onHide() {
playbackControlsFragment.hide();
onBackPressed();
}
@Override
public boolean onBackPressed() {
boolean wasExpanded = false;
if (binding.playerSlidingLayout != null) {
wasExpanded = binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED;
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
return wasExpanded;
}
@Override
public void onColorChanged(int color) {
animateColorChange(color);
playbackControlsFragment.setDark(ColorUtil.isColorLight(color));
getCallbacks().onPaletteColorChanged();
}
@Override
public void onFavoriteToggled() {
toggleFavorite(MusicPlayerRemote.getCurrentSong());
}
@Override
public void onToolbarToggled() {
toggleToolbar(binding.toolbarContainer);
}
@Override
public void onPanelSlide(View view, float slide) {
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
switch (newState) {
case COLLAPSED:
onPanelCollapsed(panel);
break;
case ANCHORED:
// this fixes a bug where the panel would get stuck for some reason
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
break;
}
}
public void onPanelCollapsed(View panel) {
resetToCurrentPosition();
}
private void resetToCurrentPosition() {
binding.playerRecyclerView.stopScroll();
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
interface Impl {
void init();
void updateCurrentSong(Song song);
void animateColorChange(final int newColor);
void setUpPanelAndAlbumCoverHeight();
}
private static abstract class BaseImpl implements Impl {
protected FlatPlayerFragment fragment;
protected FragmentFlatPlayerBinding binding;
public BaseImpl(FlatPlayerFragment fragment, FragmentFlatPlayerBinding binding) {
this.fragment = fragment;
this.binding = binding;
}
public AnimatorSet createDefaultColorChangeAnimatorSet(int newColor) {
Animator backgroundAnimator = ViewUtil.createBackgroundColorTransition(fragment.playbackControlsFragment.getView(), fragment.lastColor, newColor);
Animator statusBarAnimator = ViewUtil.createBackgroundColorTransition(binding.playerStatusBar, fragment.lastColor, newColor);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(backgroundAnimator, statusBarAnimator);
if (!ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
int adjustedLastColor = ColorUtil.isColorLight(fragment.lastColor) ? ColorUtil.darkenColor(fragment.lastColor) : fragment.lastColor;
int adjustedNewColor = ColorUtil.isColorLight(newColor) ? ColorUtil.darkenColor(newColor) : newColor;
Animator subHeaderAnimator = ViewUtil.createTextColorTransition(binding.playerQueueSubHeader, adjustedLastColor, adjustedNewColor);
animatorSet.play(subHeaderAnimator);
}
animatorSet.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME);
return animatorSet;
}
@Override
public void animateColorChange(int newColor) {
if (ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
binding.playerQueueSubHeader.setTextColor(ThemeStore.textColorSecondary(fragment.getActivity()));
}
}
}
@SuppressWarnings("ConstantConditions")
private static class PortraitImpl extends BaseImpl {
MediaEntryViewHolder currentSongViewHolder;
Song currentSong = Song.EMPTY;
public PortraitImpl(FlatPlayerFragment fragment, FragmentFlatPlayerBinding binding) {
super(fragment, binding);
}
@Override
public void init() {
currentSongViewHolder = new MediaEntryViewHolder(fragment.getView().findViewById(R.id.current_song));
currentSongViewHolder.separator.setVisibility(View.VISIBLE);
currentSongViewHolder.shortSeparator.setVisibility(View.GONE);
currentSongViewHolder.image.setScaleType(ImageView.ScaleType.CENTER);
currentSongViewHolder.image.setColorFilter(ATHUtil.resolveColor(fragment.getActivity(), R.attr.iconColor, ThemeStore.textColorSecondary(fragment.getActivity())), PorterDuff.Mode.SRC_IN);
currentSongViewHolder.image.setImageResource(R.drawable.ic_volume_up_white_24dp);
currentSongViewHolder.itemView.setOnClickListener(v -> {
// toggle the panel
if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
} else if (binding.playerSlidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
binding.playerSlidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
});
currentSongViewHolder.menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu((AppCompatActivity) fragment.getActivity()) {
@Override
public Song getSong() {
return currentSong;
}
public int getMenuRes() {
return R.menu.menu_item_playing_queue_song;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_remove_from_queue:
MusicPlayerRemote.removeFromQueue(MusicPlayerRemote.getPosition());
return true;
case R.id.action_share:
SongShareDialog.create(getSong()).show(fragment.getFragmentManager(), "SONG_SHARE_DIALOG");
return true;
}
return super.onMenuItemClick(item);
}
});
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
WidthFitSquareLayout albumCoverContainer = fragment.getView().findViewById(R.id.album_cover_container);
final int availablePanelHeight = binding.playerSlidingLayout.getHeight() - fragment.getView().findViewById(R.id.player_content).getHeight();
final int minPanelHeight = (int) ViewUtil.convertDpToPixel(8 + 72 + 24, fragment.getResources()) + fragment.getResources().getDimensionPixelSize(R.dimen.progress_container_height) + fragment.getResources().getDimensionPixelSize(R.dimen.media_controller_container_height);
if (availablePanelHeight < minPanelHeight) {
albumCoverContainer.getLayoutParams().height = albumCoverContainer.getHeight() - (minPanelHeight - availablePanelHeight);
albumCoverContainer.forceSquare(false);
}
binding.playerSlidingLayout.setPanelHeight(Math.max(minPanelHeight, availablePanelHeight));
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(binding.playerSlidingLayout.findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
currentSong = song;
currentSongViewHolder.title.setText(song.title);
currentSongViewHolder.text.setText(MusicUtil.getSongInfoString(song));
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
createDefaultColorChangeAnimatorSet(newColor).start();
}
}
@SuppressWarnings("ConstantConditions")
private static class LandscapeImpl extends BaseImpl {
public LandscapeImpl(FlatPlayerFragment fragment, FragmentFlatPlayerBinding binding) {
super(fragment, binding);
}
@Override
public void init() {
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(fragment.getView().findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
binding.playerToolbar.setTitle(song.title);
binding.playerToolbar.setSubtitle(MusicUtil.getSongInfoString(song));
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
AnimatorSet animatorSet = createDefaultColorChangeAnimatorSet(newColor);
animatorSet.play(ViewUtil.createBackgroundColorTransition(binding.playerToolbar, fragment.lastColor, newColor));
animatorSet.start();
}
}
}

View file

@ -0,0 +1,289 @@
package com.dkanada.gramophone.fragments.player.flat;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.dkanada.gramophone.databinding.FragmentFlatPlayerPlaybackControlsBinding;
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.helper.MusicProgressViewUpdateHelper;
import com.dkanada.gramophone.helper.PlayPauseButtonOnClickHandler;
import com.dkanada.gramophone.misc.SimpleOnSeekbarChangeListener;
import com.dkanada.gramophone.service.MusicService;
import com.dkanada.gramophone.fragments.AbsMusicServiceFragment;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.views.PlayPauseDrawable;
import java.util.Collection;
import java.util.LinkedList;
public class FlatPlayerPlaybackControlsFragment extends AbsMusicServiceFragment implements MusicProgressViewUpdateHelper.Callback {
public FragmentFlatPlayerPlaybackControlsBinding binding;
private PlayPauseDrawable playPauseDrawable;
private int lastPlaybackControlsColor;
private int lastDisabledPlaybackControlsColor;
private MusicProgressViewUpdateHelper progressViewUpdateHelper;
private AnimatorSet musicControllerAnimationSet;
private boolean hidden = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentFlatPlayerPlaybackControlsBinding.inflate(inflater);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setUpMusicControllers();
updateProgressTextColor();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onResume() {
super.onResume();
progressViewUpdateHelper.start();
}
@Override
public void onPause() {
super.onPause();
progressViewUpdateHelper.stop();
}
@Override
public void onServiceConnected() {
updatePlayPauseDrawableState(false);
updateRepeatState();
updateShuffleState();
}
@Override
public void onPlayStateChanged() {
updatePlayPauseDrawableState(true);
}
@Override
public void onRepeatModeChanged() {
updateRepeatState();
}
@Override
public void onShuffleModeChanged() {
updateShuffleState();
}
public void setDark(boolean dark) {
if (dark) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true);
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true);
} else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false);
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false);
}
updateRepeatState();
updateShuffleState();
updatePrevNextColor();
updatePlayPauseColor();
updateProgressTextColor();
}
private void setUpPlayPauseButton() {
playPauseDrawable = new PlayPauseDrawable(getActivity());
binding.playerPlayPauseButton.setImageDrawable(playPauseDrawable);
updatePlayPauseColor();
binding.playerPlayPauseButton.setOnClickListener(new PlayPauseButtonOnClickHandler());
binding.playerPlayPauseButton.post(() -> {
binding.playerPlayPauseButton.setPivotX(binding.playerPlayPauseButton.getWidth() / 2);
binding.playerPlayPauseButton.setPivotY(binding.playerPlayPauseButton.getHeight() / 2);
});
}
protected void updatePlayPauseDrawableState(boolean animate) {
if (MusicPlayerRemote.isPlaying()) {
playPauseDrawable.setPause(animate);
} else {
playPauseDrawable.setPlay(animate);
}
}
private void setUpMusicControllers() {
setUpPlayPauseButton();
setUpPrevNext();
setUpRepeatButton();
setUpShuffleButton();
setUpProgressSlider();
}
private void setUpPrevNext() {
updatePrevNextColor();
binding.playerNextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong());
binding.playerPrevButton.setOnClickListener(v -> MusicPlayerRemote.back());
}
private void updateProgressTextColor() {
int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false);
binding.playerSongTotalTime.setTextColor(color);
binding.playerSongCurrentProgress.setTextColor(color);
}
private void updatePrevNextColor() {
binding.playerNextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
binding.playerPrevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
}
private void updatePlayPauseColor() {
binding.playerPlayPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
}
private void setUpShuffleButton() {
binding.playerShuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode());
}
private void updateShuffleState() {
switch (MusicPlayerRemote.getShuffleMode()) {
case MusicService.SHUFFLE_MODE_SHUFFLE:
binding.playerShuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
default:
binding.playerShuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
}
}
private void setUpRepeatButton() {
binding.playerRepeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode());
}
private void updateRepeatState() {
switch (MusicPlayerRemote.getRepeatMode()) {
case MusicService.REPEAT_MODE_NONE:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp);
binding.playerRepeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
case MusicService.REPEAT_MODE_ALL:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp);
binding.playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
case MusicService.REPEAT_MODE_THIS:
binding.playerRepeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp);
binding.playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN);
break;
}
}
public void show() {
if (hidden) {
if (musicControllerAnimationSet == null) {
TimeInterpolator interpolator = new FastOutSlowInInterpolator();
final int duration = 300;
LinkedList<Animator> animators = new LinkedList<>();
addAnimation(animators, binding.playerPlayPauseButton, interpolator, duration, 0);
addAnimation(animators, binding.playerNextButton, interpolator, duration, 100);
addAnimation(animators, binding.playerPrevButton, interpolator, duration, 100);
addAnimation(animators, binding.playerShuffleButton, interpolator, duration, 200);
addAnimation(animators, binding.playerRepeatButton, interpolator, duration, 200);
musicControllerAnimationSet = new AnimatorSet();
musicControllerAnimationSet.playTogether(animators);
} else {
musicControllerAnimationSet.cancel();
}
musicControllerAnimationSet.start();
}
hidden = false;
}
public void hide() {
if (musicControllerAnimationSet != null) {
musicControllerAnimationSet.cancel();
}
prepareForAnimation(binding.playerPlayPauseButton);
prepareForAnimation(binding.playerNextButton);
prepareForAnimation(binding.playerPrevButton);
prepareForAnimation(binding.playerShuffleButton);
prepareForAnimation(binding.playerRepeatButton);
hidden = true;
}
private static void addAnimation(Collection<Animator> animators, View view, TimeInterpolator interpolator, int duration, int delay) {
Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, 0f, 1f);
scaleX.setInterpolator(interpolator);
scaleX.setDuration(duration);
scaleX.setStartDelay(delay);
animators.add(scaleX);
Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, 0f, 1f);
scaleY.setInterpolator(interpolator);
scaleY.setDuration(duration);
scaleY.setStartDelay(delay);
animators.add(scaleY);
}
private static void prepareForAnimation(View view) {
if (view != null) {
view.setScaleX(0f);
view.setScaleY(0f);
}
}
private void setUpProgressSlider() {
int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false);
binding.playerProgressSlider.getThumb().mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);
binding.playerProgressSlider.getProgressDrawable().mutate().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN);
binding.playerProgressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress);
onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis());
}
}
});
}
@Override
public void onUpdateProgressViews(int progress, int total) {
binding.playerProgressSlider.setMax(total);
binding.playerProgressSlider.setProgress(progress);
binding.playerSongTotalTime.setText(MusicUtil.getReadableDurationString(total));
binding.playerSongCurrentProgress.setText(MusicUtil.getReadableDurationString(progress));
}
}