change application id for release
This commit is contained in:
parent
0cd75de1ee
commit
9d08253655
159 changed files with 801 additions and 801 deletions
64
app/src/main/java/com/dkanada/gramophone/App.java
Normal file
64
app/src/main/java/com/dkanada/gramophone/App.java
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package com.dkanada.gramophone;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.shortcuts.DynamicShortcutManager;
|
||||
|
||||
import org.jellyfin.apiclient.interaction.AndroidConnectionManager;
|
||||
import org.jellyfin.apiclient.interaction.AndroidDevice;
|
||||
import org.jellyfin.apiclient.interaction.ApiClient;
|
||||
import org.jellyfin.apiclient.interaction.ApiEventListener;
|
||||
import org.jellyfin.apiclient.interaction.connectionmanager.ConnectionManager;
|
||||
import org.jellyfin.apiclient.interaction.device.IDevice;
|
||||
import org.jellyfin.apiclient.interaction.http.IAsyncHttpClient;
|
||||
import org.jellyfin.apiclient.model.logging.ILogger;
|
||||
import org.jellyfin.apiclient.model.serialization.IJsonSerializer;
|
||||
import org.jellyfin.apiclient.model.session.ClientCapabilities;
|
||||
|
||||
public class App extends Application {
|
||||
private static App app;
|
||||
|
||||
private static ApiClient apiClient;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = this;
|
||||
|
||||
// default theme
|
||||
if (!ThemeStore.isConfigured(this, 1)) {
|
||||
ThemeStore.editTheme(this).primaryColorRes(R.color.md_indigo_500).accentColorRes(R.color.md_pink_A400).commit();
|
||||
}
|
||||
|
||||
// dynamic shortcuts
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
new DynamicShortcutManager(this).initDynamicShortcuts();
|
||||
}
|
||||
}
|
||||
|
||||
public static ConnectionManager getConnectionManager(Context context, IJsonSerializer jsonSerializer, ILogger logger, IAsyncHttpClient httpClient) {
|
||||
String appName = context.getString(R.string.app_name);
|
||||
String appVersion = BuildConfig.VERSION_NAME;
|
||||
|
||||
IDevice device = new AndroidDevice(context);
|
||||
ClientCapabilities capabilities = new ClientCapabilities();
|
||||
ApiEventListener eventListener = new ApiEventListener();
|
||||
|
||||
return new AndroidConnectionManager(context, jsonSerializer, logger, httpClient, appName, appVersion, device, capabilities, eventListener);
|
||||
}
|
||||
|
||||
public static ApiClient getApiClient() {
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
public static void setApiClient(ApiClient client) {
|
||||
apiClient = client;
|
||||
}
|
||||
|
||||
public static App getInstance() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.misc.CustomFragmentStatePagerAdapter;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class AlbumCoverPagerAdapter extends CustomFragmentStatePagerAdapter {
|
||||
|
||||
private List<Song> dataSet;
|
||||
|
||||
private AlbumCoverFragment.ColorReceiver currentColorReceiver;
|
||||
private int currentColorReceiverPosition = -1;
|
||||
|
||||
public AlbumCoverPagerAdapter(FragmentManager fm, List<Song> dataSet) {
|
||||
super(fm);
|
||||
this.dataSet = dataSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(final int position) {
|
||||
return AlbumCoverFragment.newInstance(dataSet.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
Object o = super.instantiateItem(container, position);
|
||||
if (currentColorReceiver != null && currentColorReceiverPosition == position) {
|
||||
receiveColor(currentColorReceiver, currentColorReceiverPosition);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
// only the latest ColorReceiver is guaranteed a response
|
||||
public void receiveColor(AlbumCoverFragment.ColorReceiver colorReceiver, int position) {
|
||||
AlbumCoverFragment fragment = (AlbumCoverFragment) getFragment(position);
|
||||
if (fragment != null) {
|
||||
currentColorReceiver = null;
|
||||
currentColorReceiverPosition = -1;
|
||||
fragment.receiveColor(colorReceiver, position);
|
||||
} else {
|
||||
currentColorReceiver = colorReceiver;
|
||||
currentColorReceiverPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AlbumCoverFragment extends Fragment {
|
||||
private static final String SONG_ARG = "song";
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
@BindView(R.id.player_image)
|
||||
ImageView albumCover;
|
||||
|
||||
private boolean isColorReady;
|
||||
private int color;
|
||||
private Song song;
|
||||
private ColorReceiver colorReceiver;
|
||||
private int request;
|
||||
|
||||
public static AlbumCoverFragment newInstance(final Song song) {
|
||||
AlbumCoverFragment frag = new AlbumCoverFragment();
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(SONG_ARG, song);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
song = getArguments().getParcelable(SONG_ARG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_album_cover, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
loadAlbumCover();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
colorReceiver = null;
|
||||
}
|
||||
|
||||
private void loadAlbumCover() {
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(getContext()), song.primary)
|
||||
.generatePalette(getActivity()).build()
|
||||
.into(new CustomPaletteTarget(albumCover) {
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
setColor(color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setColor(int color) {
|
||||
this.color = color;
|
||||
isColorReady = true;
|
||||
if (colorReceiver != null) {
|
||||
colorReceiver.onColorReady(color, request);
|
||||
colorReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveColor(ColorReceiver colorReceiver, int request) {
|
||||
if (isColorReady) {
|
||||
colorReceiver.onColorReady(color, request);
|
||||
} else {
|
||||
this.colorReceiver = colorReceiver;
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ColorReceiver {
|
||||
void onColorReady(int color, int request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.CategoryInfo;
|
||||
import com.dkanada.gramophone.helper.SwipeAndDragHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder> implements SwipeAndDragHelper.ActionCompletionContract {
|
||||
private List<CategoryInfo> categoryInfos;
|
||||
private ItemTouchHelper touchHelper;
|
||||
|
||||
public CategoryInfoAdapter(List<CategoryInfo> categoryInfos) {
|
||||
this.categoryInfos = categoryInfos;
|
||||
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
|
||||
touchHelper = new ItemTouchHelper(swipeAndDragHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CategoryInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.preference_dialog_library_categories_listitem, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
|
||||
CategoryInfo categoryInfo = categoryInfos.get(position);
|
||||
|
||||
holder.checkBox.setChecked(categoryInfo.visible);
|
||||
holder.title.setText(holder.title.getResources().getString(categoryInfo.category.stringRes));
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
|
||||
categoryInfo.visible = !categoryInfo.visible;
|
||||
holder.checkBox.setChecked(categoryInfo.visible);
|
||||
} else {
|
||||
Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
holder.dragView.setOnTouchListener((view, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
touchHelper.startDrag(holder);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return categoryInfos.size();
|
||||
}
|
||||
|
||||
public void setCategoryInfos(List<CategoryInfo> categoryInfos) {
|
||||
this.categoryInfos = categoryInfos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewMoved(int oldPosition, int newPosition) {
|
||||
CategoryInfo categoryInfo = categoryInfos.get(oldPosition);
|
||||
categoryInfos.remove(oldPosition);
|
||||
categoryInfos.add(newPosition, categoryInfo);
|
||||
notifyItemMoved(oldPosition, newPosition);
|
||||
}
|
||||
|
||||
public void attachToRecyclerView(RecyclerView recyclerView) {
|
||||
touchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
public List<CategoryInfo> getCategoryInfos() {
|
||||
return categoryInfos;
|
||||
}
|
||||
|
||||
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
|
||||
if (categoryInfo.visible) {
|
||||
for (CategoryInfo c : categoryInfos) {
|
||||
if (c != categoryInfo && c.visible) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public CheckBox checkBox;
|
||||
public TextView title;
|
||||
public View dragView;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
checkBox = view.findViewById(R.id.checkbox);
|
||||
title = view.findViewById(R.id.title);
|
||||
dragView = view.findViewById(R.id.drag_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.model.Genre;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder> implements FastScrollRecyclerView.SectionedAdapter {
|
||||
private final AppCompatActivity activity;
|
||||
private List<Genre> dataSet;
|
||||
|
||||
private int itemLayoutRes;
|
||||
|
||||
public GenreAdapter(@NonNull AppCompatActivity activity, List<Genre> dataSet, @LayoutRes int itemLayoutRes) {
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
this.itemLayoutRes = itemLayoutRes;
|
||||
}
|
||||
|
||||
public List<Genre> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Genre> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return dataSet.get(position).hashCode();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
final Genre genre = dataSet.get(position);
|
||||
|
||||
if (holder.getAdapterPosition() == getItemCount() - 1) {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (holder.menu != null) {
|
||||
holder.menu.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(genre.name);
|
||||
}
|
||||
|
||||
loadImage(genre, holder);
|
||||
}
|
||||
|
||||
protected void loadImage(Genre genre, final GenreAdapter.ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), genre.id)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSectionName(int position) {
|
||||
final Genre genre = dataSet.get(position);
|
||||
return genre.id.hashCode() == -1 ? "" : MusicUtil.getSectionName(dataSet.get(position).name);
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Genre genre = dataSet.get(getAdapterPosition());
|
||||
NavigationUtil.goToGenre(activity, genre);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.dkanada.gramophone.model.CategoryInfo;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.pager.AlbumsFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.pager.ArtistsFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.pager.GenresFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.pager.PlaylistsFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.pager.SongsFragment;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private final SparseArray<WeakReference<Fragment>> mFragmentArray = new SparseArray<>();
|
||||
|
||||
private final List<Holder> mHolderList = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
|
||||
public MusicLibraryPagerAdapter(@NonNull final Context context, final FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
mContext = context;
|
||||
setCategoryInfos(PreferenceUtil.getInstance(context).getLibraryCategories());
|
||||
}
|
||||
|
||||
public void setCategoryInfos(@NonNull List<CategoryInfo> categoryInfos) {
|
||||
mHolderList.clear();
|
||||
|
||||
for (CategoryInfo categoryInfo : categoryInfos) {
|
||||
if (categoryInfo.visible) {
|
||||
MusicFragments fragment = MusicFragments.valueOf(categoryInfo.category.toString());
|
||||
Holder holder = new Holder();
|
||||
holder.mClassName = fragment.getFragmentClass().getName();
|
||||
holder.title = mContext.getResources()
|
||||
.getString(categoryInfo.category.stringRes)
|
||||
.toUpperCase(Locale.getDefault());
|
||||
mHolderList.add(holder);
|
||||
}
|
||||
}
|
||||
|
||||
alignCache();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Fragment getFragment(final int position) {
|
||||
final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position);
|
||||
if (mWeakFragment != null && mWeakFragment.get() != null) {
|
||||
return mWeakFragment.get();
|
||||
}
|
||||
|
||||
return getItem(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(@NonNull Object fragment) {
|
||||
for (int i = 0, size = mHolderList.size(); i < size; i++) {
|
||||
Holder holder = mHolderList.get(i);
|
||||
if (holder.mClassName.equals(fragment.getClass().getName())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return POSITION_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// as fragment position is not fixed, we can't use position as id
|
||||
return MusicFragments.of(getFragment(position).getClass()).ordinal();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
|
||||
final Fragment mFragment = (Fragment) super.instantiateItem(container, position);
|
||||
final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position);
|
||||
if (mWeakFragment != null) {
|
||||
mWeakFragment.clear();
|
||||
}
|
||||
|
||||
mFragmentArray.put(position, new WeakReference<>(mFragment));
|
||||
return mFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(final int position) {
|
||||
final Holder mCurrentHolder = mHolderList.get(position);
|
||||
return Fragment.instantiate(mContext, mCurrentHolder.mClassName, mCurrentHolder.mParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(final ViewGroup container, final int position, final Object object) {
|
||||
super.destroyItem(container, position, object);
|
||||
final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position);
|
||||
if (mWeakFragment != null) {
|
||||
mWeakFragment.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mHolderList.size();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CharSequence getPageTitle(final int position) {
|
||||
return mHolderList.get(position).title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns the fragment cache with the current category layout.
|
||||
*/
|
||||
private void alignCache() {
|
||||
if (mFragmentArray.size() == 0) return;
|
||||
|
||||
HashMap<String, WeakReference<Fragment>> mappings = new HashMap<>(mFragmentArray.size());
|
||||
for (int i = 0, size = mFragmentArray.size(); i < size; i++) {
|
||||
WeakReference<Fragment> ref = mFragmentArray.valueAt(i);
|
||||
Fragment fragment = ref.get();
|
||||
if (fragment != null) {
|
||||
mappings.put(fragment.getClass().getName(), ref);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, size = mHolderList.size(); i < size; i++) {
|
||||
WeakReference<Fragment> ref = mappings.get(mHolderList.get(i).mClassName);
|
||||
if (ref != null) {
|
||||
mFragmentArray.put(i, ref);
|
||||
} else {
|
||||
mFragmentArray.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MusicFragments {
|
||||
SONGS(SongsFragment.class),
|
||||
ALBUMS(AlbumsFragment.class),
|
||||
ARTISTS(ArtistsFragment.class),
|
||||
GENRES(GenresFragment.class),
|
||||
PLAYLISTS(PlaylistsFragment.class);
|
||||
|
||||
private final Class<? extends Fragment> mFragmentClass;
|
||||
|
||||
MusicFragments(final Class<? extends Fragment> fragmentClass) {
|
||||
mFragmentClass = fragmentClass;
|
||||
}
|
||||
|
||||
public Class<? extends Fragment> getFragmentClass() {
|
||||
return mFragmentClass;
|
||||
}
|
||||
|
||||
public static MusicFragments of(Class<?> cl) {
|
||||
MusicFragments[] fragments = All.FRAGMENTS;
|
||||
for (MusicFragments fragment : fragments) {
|
||||
if (cl.equals(fragment.mFragmentClass)) {
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown music fragment " + cl);
|
||||
}
|
||||
|
||||
private static class All {
|
||||
public static final MusicFragments[] FRAGMENTS = values();
|
||||
}
|
||||
}
|
||||
|
||||
private final static class Holder {
|
||||
String mClassName;
|
||||
Bundle mParams;
|
||||
String title;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.base.AbsMultiSelectAdapter;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.dialogs.DeletePlaylistDialog;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.helper.menu.PlaylistMenuHelper;
|
||||
import com.dkanada.gramophone.helper.menu.SongsMenuHelper;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PlaylistAdapter extends AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, Playlist> {
|
||||
protected final AppCompatActivity activity;
|
||||
protected List<Playlist> dataSet;
|
||||
protected int itemLayoutRes;
|
||||
|
||||
public PlaylistAdapter(AppCompatActivity activity, List<Playlist> dataSet, @LayoutRes int itemLayoutRes, @Nullable CabHolder cabHolder) {
|
||||
super(activity, cabHolder, R.menu.menu_playlists_selection);
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
this.itemLayoutRes = itemLayoutRes;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public List<Playlist> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Playlist> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return dataSet.get(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false);
|
||||
return createViewHolder(view, viewType);
|
||||
}
|
||||
|
||||
protected ViewHolder createViewHolder(View view, int viewType) {
|
||||
return new ViewHolder(view, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
final Playlist playlist = dataSet.get(position);
|
||||
|
||||
holder.itemView.setActivated(isChecked(playlist));
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(playlist.name);
|
||||
}
|
||||
|
||||
if (holder.getAdapterPosition() == getItemCount() - 1) {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
loadImage(playlist, holder);
|
||||
}
|
||||
|
||||
protected void loadImage(Playlist playlist, final PlaylistAdapter.ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), playlist.id)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Playlist getIdentifier(int position) {
|
||||
return dataSet.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(Playlist playlist) {
|
||||
return playlist.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Playlist> selection) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.action_delete_playlist:
|
||||
DeletePlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST");
|
||||
break;
|
||||
default:
|
||||
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Song> getSongList(@NonNull List<Playlist> playlists) {
|
||||
final List<Song> songs = new ArrayList<>();
|
||||
return songs;
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
public ViewHolder(@NonNull View itemView, int itemViewType) {
|
||||
super(itemView);
|
||||
|
||||
if (menu != null) {
|
||||
menu.setOnClickListener(view -> {
|
||||
final PopupMenu popupMenu = new PopupMenu(activity, view);
|
||||
|
||||
popupMenu.inflate(R.menu.menu_item_playlist);
|
||||
popupMenu.setOnMenuItemClickListener(item -> PlaylistMenuHelper.handleMenuClick(activity, dataSet.get(getAdapterPosition()), item));
|
||||
|
||||
popupMenu.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (isInQuickSelectMode()) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
} else {
|
||||
Playlist playlist = dataSet.get(getAdapterPosition());
|
||||
NavigationUtil.goToPlaylist(activity, playlist);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.dkanada.gramophone.adapter;
|
||||
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.kabouzeid.appthemehelper.util.ATHUtil;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.helper.menu.SongMenuHelper;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.ViewHolder> {
|
||||
|
||||
private static final int HEADER = 0;
|
||||
private static final int ALBUM = 1;
|
||||
private static final int ARTIST = 2;
|
||||
private static final int SONG = 3;
|
||||
|
||||
private final AppCompatActivity activity;
|
||||
private List<Object> dataSet;
|
||||
|
||||
public SearchAdapter(@NonNull AppCompatActivity activity, @NonNull List<Object> dataSet) {
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
}
|
||||
|
||||
public void swapDataSet(@NonNull List<Object> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (dataSet.get(position) instanceof Album) return ALBUM;
|
||||
if (dataSet.get(position) instanceof Artist) return ARTIST;
|
||||
if (dataSet.get(position) instanceof Song) return SONG;
|
||||
return HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == HEADER)
|
||||
return new ViewHolder(LayoutInflater.from(activity).inflate(R.layout.sub_header, parent, false), viewType);
|
||||
return new ViewHolder(LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), viewType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
switch (getItemViewType(position)) {
|
||||
case ALBUM:
|
||||
final Album album = (Album) dataSet.get(position);
|
||||
holder.title.setText(album.getTitle());
|
||||
holder.text.setText(MusicUtil.getAlbumInfoString(activity, album));
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), album.primary)
|
||||
.build().into(holder.image);
|
||||
break;
|
||||
case ARTIST:
|
||||
final Artist artist = (Artist) dataSet.get(position);
|
||||
holder.title.setText(artist.getName());
|
||||
holder.text.setText(MusicUtil.getArtistInfoString(activity, artist));
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), artist.primary)
|
||||
.build().into(holder.image);
|
||||
break;
|
||||
case SONG:
|
||||
final Song song = (Song) dataSet.get(position);
|
||||
holder.title.setText(song.title);
|
||||
holder.text.setText(MusicUtil.getSongInfoString(song));
|
||||
break;
|
||||
default:
|
||||
holder.title.setText(dataSet.get(position).toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
public ViewHolder(@NonNull View itemView, int itemViewType) {
|
||||
super(itemView);
|
||||
itemView.setOnLongClickListener(null);
|
||||
|
||||
if (itemViewType != HEADER) {
|
||||
itemView.setBackgroundColor(ATHUtil.resolveColor(activity, R.attr.cardBackgroundColor));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
itemView.setElevation(activity.getResources().getDimensionPixelSize(R.dimen.card_elevation));
|
||||
}
|
||||
if (shortSeparator != null) {
|
||||
shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (menu != null) {
|
||||
if (itemViewType == SONG) {
|
||||
menu.setVisibility(View.VISIBLE);
|
||||
menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) {
|
||||
@Override
|
||||
public Song getSong() {
|
||||
return (Song) dataSet.get(getAdapterPosition());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
menu.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
switch (itemViewType) {
|
||||
case ALBUM:
|
||||
setImageTransitionName(activity.getString(R.string.transition_album_art));
|
||||
break;
|
||||
case ARTIST:
|
||||
setImageTransitionName(activity.getString(R.string.transition_artist_image));
|
||||
break;
|
||||
default:
|
||||
View container = itemView.findViewById(R.id.image_container);
|
||||
if (container != null) {
|
||||
container.setVisibility(View.GONE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Object item = dataSet.get(getAdapterPosition());
|
||||
switch (getItemViewType()) {
|
||||
case ALBUM:
|
||||
NavigationUtil.goToAlbum(activity,
|
||||
(Album) item,
|
||||
Pair.create(image, activity.getResources().getString(R.string.transition_album_art)));
|
||||
break;
|
||||
case ARTIST:
|
||||
NavigationUtil.goToArtist(activity,
|
||||
(Artist) item,
|
||||
Pair.create(image, activity.getResources().getString(R.string.transition_artist_image)));
|
||||
break;
|
||||
case SONG:
|
||||
List<Song> playList = new ArrayList<>();
|
||||
playList.add((Song) item);
|
||||
MusicPlayerRemote.openQueue(playList, 0, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
package com.dkanada.gramophone.adapter.album;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.base.AbsMultiSelectAdapter;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.helper.menu.SongsMenuHelper;
|
||||
import com.dkanada.gramophone.helper.sort.SortMethod;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumAdapter extends AbsMultiSelectAdapter<AlbumAdapter.ViewHolder, Album> implements FastScrollRecyclerView.SectionedAdapter {
|
||||
protected final AppCompatActivity activity;
|
||||
protected List<Album> dataSet;
|
||||
|
||||
protected int itemLayoutRes;
|
||||
|
||||
protected boolean usePalette;
|
||||
|
||||
public AlbumAdapter(@NonNull AppCompatActivity activity, List<Album> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, cabHolder, R.menu.menu_media_selection);
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
this.itemLayoutRes = itemLayoutRes;
|
||||
this.usePalette = usePalette;
|
||||
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void usePalette(boolean usePalette) {
|
||||
this.usePalette = usePalette;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Album> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<Album> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false);
|
||||
return createViewHolder(view, viewType);
|
||||
}
|
||||
|
||||
protected ViewHolder createViewHolder(View view, int viewType) {
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
protected String getAlbumTitle(Album album) {
|
||||
return album.getTitle();
|
||||
}
|
||||
|
||||
protected String getAlbumText(Album album) {
|
||||
return MusicUtil.getAlbumInfoString(activity, album);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
final Album album = dataSet.get(position);
|
||||
|
||||
final boolean isChecked = isChecked(album);
|
||||
holder.itemView.setActivated(isChecked);
|
||||
|
||||
if (holder.getAdapterPosition() == getItemCount() - 1) {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(getAlbumTitle(album));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setText(getAlbumText(album));
|
||||
}
|
||||
|
||||
loadAlbumCover(album, holder);
|
||||
}
|
||||
|
||||
protected void setColors(int color, ViewHolder holder) {
|
||||
if (holder.paletteColorContainer != null) {
|
||||
holder.paletteColorContainer.setBackgroundColor(color);
|
||||
if (holder.title != null) {
|
||||
holder.title.setTextColor(MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setTextColor(MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadAlbumCover(Album album, final ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), album.primary)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
if (usePalette) {
|
||||
setColors(color, holder);
|
||||
} else {
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return dataSet.get(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Album getIdentifier(int position) {
|
||||
return dataSet.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(Album album) {
|
||||
return album.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Album> selection) {
|
||||
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Song> getSongList(@NonNull List<Album> albums) {
|
||||
final List<Song> songs = new ArrayList<>();
|
||||
for (Album album : albums) {
|
||||
songs.addAll(album.songs);
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSectionName(int position) {
|
||||
@Nullable String sectionName = null;
|
||||
switch (PreferenceUtil.getInstance(activity).getAlbumSortMethod()) {
|
||||
case SortMethod.NAME:
|
||||
sectionName = dataSet.get(position).getTitle();
|
||||
break;
|
||||
case SortMethod.ARTIST:
|
||||
sectionName = dataSet.get(position).getArtistName();
|
||||
break;
|
||||
case SortMethod.YEAR:
|
||||
return MusicUtil.getYearString(dataSet.get(position).getYear());
|
||||
case SortMethod.RANDOM:
|
||||
return activity.getResources().getString(R.string.random);
|
||||
}
|
||||
|
||||
return MusicUtil.getSectionName(sectionName);
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
public ViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
setImageTransitionName(activity.getString(R.string.transition_album_art));
|
||||
if (menu != null) {
|
||||
menu.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (isInQuickSelectMode()) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
} else {
|
||||
Pair[] albumPairs = new Pair[]{Pair.create(image, activity.getResources().getString(R.string.transition_album_art))};
|
||||
NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition()), albumPairs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.dkanada.gramophone.adapter.album;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.helper.HorizontalAdapterHelper;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HorizontalAlbumAdapter extends AlbumAdapter {
|
||||
public HorizontalAlbumAdapter(@NonNull AppCompatActivity activity, List<Album> dataSet, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, usePalette, cabHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewHolder createViewHolder(View view, int viewType) {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
HorizontalAdapterHelper.applyMarginToLayoutParams(activity, params, viewType);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setColors(int color, ViewHolder holder) {
|
||||
if (holder.itemView != null) {
|
||||
CardView card=(CardView)holder.itemView;
|
||||
card.setCardBackgroundColor(color);
|
||||
if (holder.title != null) {
|
||||
holder.title.setTextColor(MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setTextColor(MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAlbumCover(Album album, final ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), album.primary)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
setColors(getAlbumArtistFooterColor(), holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
if (usePalette) {
|
||||
setColors(color, holder);
|
||||
} else {
|
||||
setColors(getAlbumArtistFooterColor(), holder);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAlbumText(Album album) {
|
||||
return MusicUtil.getYearString(album.getYear());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return HorizontalAdapterHelper.getItemViewtype(position, getItemCount());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
package com.dkanada.gramophone.adapter.artist;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.base.AbsMultiSelectAdapter;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.helper.menu.SongsMenuHelper;
|
||||
import com.dkanada.gramophone.helper.sort.SortMethod;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ArtistAdapter extends AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist> implements FastScrollRecyclerView.SectionedAdapter {
|
||||
protected final AppCompatActivity activity;
|
||||
protected List<Artist> dataSet;
|
||||
|
||||
protected int itemLayoutRes;
|
||||
|
||||
protected boolean usePalette;
|
||||
|
||||
public ArtistAdapter(@NonNull AppCompatActivity activity, List<Artist> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, cabHolder, R.menu.menu_media_selection);
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
this.itemLayoutRes = itemLayoutRes;
|
||||
this.usePalette = usePalette;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Artist> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<Artist> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public void usePalette(boolean usePalette) {
|
||||
this.usePalette = usePalette;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return dataSet.get(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false);
|
||||
return createViewHolder(view);
|
||||
}
|
||||
|
||||
protected ViewHolder createViewHolder(View view) {
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
final Artist artist = dataSet.get(position);
|
||||
|
||||
boolean isChecked = isChecked(artist);
|
||||
holder.itemView.setActivated(isChecked);
|
||||
|
||||
if (holder.getAdapterPosition() == getItemCount() - 1) {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(artist.getName());
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setText(MusicUtil.getArtistInfoString(activity, artist));
|
||||
}
|
||||
|
||||
holder.itemView.setActivated(isChecked(artist));
|
||||
loadArtistImage(artist, holder);
|
||||
}
|
||||
|
||||
private void setColors(int color, ViewHolder holder) {
|
||||
if (holder.paletteColorContainer != null) {
|
||||
holder.paletteColorContainer.setBackgroundColor(color);
|
||||
if (holder.title != null) {
|
||||
holder.title.setTextColor(MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setTextColor(MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadArtistImage(Artist artist, final ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), artist.primary)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
if (usePalette) {
|
||||
setColors(color, holder);
|
||||
} else {
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Artist getIdentifier(int position) {
|
||||
return dataSet.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(Artist artist) {
|
||||
return artist.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Artist> selection) {
|
||||
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Song> getSongList(@NonNull List<Artist> artists) {
|
||||
final List<Song> songs = new ArrayList<>();
|
||||
for (Artist artist : artists) {
|
||||
songs.addAll(artist.getSongs());
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSectionName(int position) {
|
||||
return MusicUtil.getSectionName(dataSet.get(position).getName());
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
setImageTransitionName(activity.getString(R.string.transition_artist_image));
|
||||
if (menu != null) {
|
||||
menu.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (isInQuickSelectMode()) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
} else {
|
||||
Pair[] artistPairs = new Pair[]{Pair.create(image, activity.getResources().getString(R.string.transition_artist_image))};
|
||||
NavigationUtil.goToArtist(activity, dataSet.get(getAdapterPosition()), artistPairs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.dkanada.gramophone.adapter.base;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.MenuRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbsMultiSelectAdapter<VH extends RecyclerView.ViewHolder, I> extends RecyclerView.Adapter<VH> implements MaterialCab.Callback {
|
||||
private final Context context;
|
||||
private final CabHolder cabHolder;
|
||||
private int menuRes;
|
||||
|
||||
private MaterialCab cab;
|
||||
private List<I> checked;
|
||||
|
||||
public AbsMultiSelectAdapter(Context context, @Nullable CabHolder cabHolder, @MenuRes int menuRes) {
|
||||
this.context = context;
|
||||
this.cabHolder = cabHolder;
|
||||
this.menuRes = menuRes;
|
||||
|
||||
this.checked = new ArrayList<>();
|
||||
}
|
||||
|
||||
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
|
||||
this.menuRes = menuRes;
|
||||
}
|
||||
|
||||
protected boolean toggleChecked(final int position) {
|
||||
if (cabHolder != null) {
|
||||
I identifier = getIdentifier(position);
|
||||
if (identifier == null) return false;
|
||||
|
||||
if (!checked.remove(identifier)) checked.add(identifier);
|
||||
|
||||
notifyItemChanged(position);
|
||||
updateCab();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void checkAll() {
|
||||
if (cabHolder != null) {
|
||||
checked.clear();
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
I identifier = getIdentifier(i);
|
||||
if (identifier != null) {
|
||||
checked.add(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
updateCab();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCab() {
|
||||
if (cabHolder != null) {
|
||||
if (cab == null || !cab.isActive()) {
|
||||
cab = cabHolder.openCab(menuRes, this);
|
||||
}
|
||||
|
||||
final int size = checked.size();
|
||||
if (size <= 0) cab.finish();
|
||||
else if (size == 1) cab.setTitle(getName(checked.get(0)));
|
||||
else cab.setTitle(context.getString(R.string.x_selected, size));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearChecked() {
|
||||
checked.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected boolean isChecked(I identifier) {
|
||||
return checked.contains(identifier);
|
||||
}
|
||||
|
||||
protected boolean isInQuickSelectMode() {
|
||||
return cab != null && cab.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabItemClicked(MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) {
|
||||
checkAll();
|
||||
} else {
|
||||
onMultipleItemAction(menuItem, new ArrayList<>(checked));
|
||||
cab.finish();
|
||||
clearChecked();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabFinished(MaterialCab materialCab) {
|
||||
clearChecked();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected String getName(I object) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract I getIdentifier(int position);
|
||||
|
||||
protected abstract void onMultipleItemAction(MenuItem menuItem, List<I> selection);
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.dkanada.gramophone.adapter.base;
|
||||
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MediaEntryViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||
@Nullable
|
||||
@BindView(R.id.image)
|
||||
public ImageView image;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.image_text)
|
||||
public TextView imageText;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.title)
|
||||
public TextView title;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.text)
|
||||
public TextView text;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.menu)
|
||||
public View menu;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.separator)
|
||||
public View separator;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.short_separator)
|
||||
public View shortSeparator;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.drag_view)
|
||||
public View dragView;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.palette_color_container)
|
||||
public View paletteColorContainer;
|
||||
|
||||
public MediaEntryViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
|
||||
itemView.setOnClickListener(this);
|
||||
itemView.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
protected void setImageTransitionName(@NonNull String transitionName) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && image != null) {
|
||||
image.setTransitionName(transitionName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbsOffsetSongAdapter extends SongAdapter {
|
||||
|
||||
protected static final int OFFSET_ITEM = 0;
|
||||
protected static final int SONG = 1;
|
||||
|
||||
public AbsOffsetSongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder);
|
||||
}
|
||||
|
||||
public AbsOffsetSongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder, boolean showSectionName) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder, showSectionName);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SongAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == OFFSET_ITEM) {
|
||||
View view = LayoutInflater.from(activity).inflate(R.layout.item_list_single_row, parent, false);
|
||||
return createViewHolder(view);
|
||||
}
|
||||
|
||||
return super.onCreateViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new AbsOffsetSongAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
position--;
|
||||
|
||||
if (position < 0) return -2;
|
||||
return super.getItemId(position);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Song getIdentifier(int position) {
|
||||
position--;
|
||||
|
||||
if (position < 0) return null;
|
||||
return super.getIdentifier(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int superItemCount = super.getItemCount();
|
||||
return superItemCount == 0 ? 0 : superItemCount + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position == 0 ? OFFSET_ITEM : SONG;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSectionName(int position) {
|
||||
position--;
|
||||
|
||||
if (position < 0) return "";
|
||||
return super.getSectionName(position);
|
||||
}
|
||||
|
||||
public class ViewHolder extends SongAdapter.ViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Song getSong() {
|
||||
// return empty song just to be safe
|
||||
if (getItemViewType() == OFFSET_ITEM) return Song.EMPTY_SONG;
|
||||
return dataSet.get(getAdapterPosition() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (isInQuickSelectMode() && getItemViewType() != OFFSET_ITEM) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
} else {
|
||||
MusicPlayerRemote.openQueue(dataSet, getAdapterPosition() - 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (getItemViewType() == OFFSET_ITEM) return false;
|
||||
toggleChecked(getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumSongAdapter extends SongAdapter {
|
||||
public AlbumSongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SongAdapter.ViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
final Song song = dataSet.get(position);
|
||||
|
||||
if (holder.imageText != null) {
|
||||
final int trackNumber = MusicUtil.getFixedTrackNumber(song.trackNumber);
|
||||
final String trackNumberString = trackNumber > 0 ? String.valueOf(trackNumber) : "-";
|
||||
holder.imageText.setText(trackNumberString);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSongText(Song song) {
|
||||
return MusicUtil.getReadableDurationString(song.duration);
|
||||
}
|
||||
|
||||
public class ViewHolder extends SongAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
if (imageText != null) {
|
||||
imageText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (image != null) {
|
||||
image.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAlbumCover(Song song, SongAdapter.ViewHolder holder) {
|
||||
// don't want to load it in this adapter
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.helper.menu.SongMenuHelper;
|
||||
import com.dkanada.gramophone.helper.menu.SongsMenuHelper;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ArtistSongAdapter extends ArrayAdapter<Song> implements MaterialCab.Callback {
|
||||
@Nullable
|
||||
private final CabHolder cabHolder;
|
||||
private MaterialCab cab;
|
||||
private List<Song> dataSet;
|
||||
private List<Song> checked;
|
||||
|
||||
@NonNull
|
||||
private final AppCompatActivity activity;
|
||||
|
||||
public ArtistSongAdapter(@NonNull AppCompatActivity activity, @NonNull List<Song> dataSet, @Nullable CabHolder cabHolder) {
|
||||
super(activity, R.layout.item_list, dataSet);
|
||||
this.activity = activity;
|
||||
this.cabHolder = cabHolder;
|
||||
this.dataSet = dataSet;
|
||||
checked = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<Song> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Song> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
clear();
|
||||
addAll(dataSet);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public View getView(final int position, View convertView, @NonNull ViewGroup parent) {
|
||||
final Song song = getItem(position);
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_list, parent, false);
|
||||
}
|
||||
|
||||
final TextView songTitle = convertView.findViewById(R.id.title);
|
||||
final TextView songInfo = convertView.findViewById(R.id.text);
|
||||
final ImageView albumArt = convertView.findViewById(R.id.image);
|
||||
final View shortSeparator = convertView.findViewById(R.id.short_separator);
|
||||
|
||||
if (position == getCount() - 1) {
|
||||
if (shortSeparator != null) {
|
||||
shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (shortSeparator != null) {
|
||||
shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
songTitle.setText(song.title);
|
||||
songInfo.setText(song.albumName);
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), song.primary)
|
||||
.build().into(albumArt);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
albumArt.setTransitionName(activity.getString(R.string.transition_album_art));
|
||||
}
|
||||
|
||||
final ImageView overflowButton = convertView.findViewById(R.id.menu);
|
||||
overflowButton.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) {
|
||||
@Override
|
||||
public Song getSong() {
|
||||
return song;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_go_to_album) {
|
||||
Pair[] albumPairs = new Pair[]{Pair.create(albumArt, activity.getResources().getString(R.string.transition_album_art))};
|
||||
NavigationUtil.goToAlbum(activity, new Album(song), albumPairs);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onMenuItemClick(item);
|
||||
}
|
||||
});
|
||||
|
||||
convertView.setActivated(isChecked(song));
|
||||
convertView.setOnClickListener(view -> {
|
||||
if (isInQuickSelectMode()) {
|
||||
toggleChecked(song);
|
||||
} else {
|
||||
MusicPlayerRemote.openQueue(dataSet, position, true);
|
||||
}
|
||||
});
|
||||
|
||||
convertView.setOnLongClickListener(view -> {
|
||||
toggleChecked(song);
|
||||
return true;
|
||||
});
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Song> selection) {
|
||||
SongsMenuHelper.handleMenuClick(activity, selection, menuItem.getItemId());
|
||||
}
|
||||
|
||||
protected void toggleChecked(Song song) {
|
||||
if (cabHolder != null) {
|
||||
openCabIfNecessary();
|
||||
|
||||
if (!checked.remove(song)) checked.add(song);
|
||||
notifyDataSetChanged();
|
||||
|
||||
final int size = checked.size();
|
||||
if (size <= 0) cab.finish();
|
||||
else if (size == 1) cab.setTitle(checked.get(0).title);
|
||||
else if (size > 1) cab.setTitle(String.valueOf(size));
|
||||
}
|
||||
}
|
||||
|
||||
private void openCabIfNecessary() {
|
||||
if (cabHolder != null) {
|
||||
if (cab == null || !cab.isActive()) {
|
||||
cab = cabHolder.openCab(R.menu.menu_media_selection, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unCheckAll() {
|
||||
checked.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected boolean isChecked(Song song) {
|
||||
return checked.contains(song);
|
||||
}
|
||||
|
||||
protected boolean isInQuickSelectMode() {
|
||||
return cab != null && cab.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabItemClicked(@NonNull MenuItem menuItem) {
|
||||
onMultipleItemAction(menuItem, new ArrayList<>(checked));
|
||||
cab.finish();
|
||||
unCheckAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabFinished(MaterialCab materialCab) {
|
||||
unCheckAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.dialogs.RemoveFromPlaylistDialog;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class OrderablePlaylistSongAdapter extends PlaylistSongAdapter implements DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
|
||||
|
||||
private OnMoveItemListener onMoveItemListener;
|
||||
|
||||
public OrderablePlaylistSongAdapter(@NonNull AppCompatActivity activity, @NonNull List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder, @Nullable OnMoveItemListener onMoveItemListener) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder);
|
||||
setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection);
|
||||
this.onMoveItemListener = onMoveItemListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new OrderablePlaylistSongAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
position--;
|
||||
|
||||
if (position < 0) return -2;
|
||||
return dataSet.get(position).id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Song> selection) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.action_remove_from_playlist:
|
||||
RemoveFromPlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return;
|
||||
}
|
||||
|
||||
super.onMultipleItemAction(menuItem, selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(ViewHolder holder, int position, int x, int y) {
|
||||
return onMoveItemListener != null && position > 0 &&
|
||||
(ViewUtil.hitTest(holder.dragView, x, y) || ViewUtil.hitTest(holder.image, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(ViewHolder holder, int position) {
|
||||
return new ItemDraggableRange(1, dataSet.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
if (onMoveItemListener != null && fromPosition != toPosition) {
|
||||
onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
|
||||
return dropPosition > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragStarted(int position) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public interface OnMoveItemListener {
|
||||
void onMoveItem(int fromPosition, int toPosition);
|
||||
}
|
||||
|
||||
public class ViewHolder extends PlaylistSongAdapter.ViewHolder implements DraggableItemViewHolder {
|
||||
@DraggableItemStateFlags
|
||||
private int mDragStateFlags;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
if (dragView != null) {
|
||||
if (onMoveItemListener != null) {
|
||||
dragView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
dragView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSongMenuRes() {
|
||||
return R.menu.menu_item_playlist_song;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSongMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_remove_from_playlist:
|
||||
RemoveFromPlaylistDialog.create(getSong()).show(activity.getSupportFragmentManager(), "REMOVE_FROM_PLAYLIST");
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onSongMenuItemClick(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDragStateFlags(@DraggableItemStateFlags int flags) {
|
||||
mDragStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DraggableItemStateFlags
|
||||
public int getDragStateFlags() {
|
||||
return mDragStateFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PlayingQueueAdapter extends SongAdapter implements DraggableItemAdapter<PlayingQueueAdapter.ViewHolder> {
|
||||
private static final int HISTORY = 0;
|
||||
private static final int CURRENT = 1;
|
||||
private static final int UP_NEXT = 2;
|
||||
|
||||
private int current;
|
||||
|
||||
public PlayingQueueAdapter(AppCompatActivity activity, List<Song> dataSet, int current, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder);
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SongAdapter.ViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
if (holder.imageText != null) {
|
||||
holder.imageText.setText(String.valueOf(position - current));
|
||||
}
|
||||
if (holder.getItemViewType() == HISTORY || holder.getItemViewType() == CURRENT) {
|
||||
setAlpha(holder, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position < current) {
|
||||
return HISTORY;
|
||||
} else if (position > current) {
|
||||
return UP_NEXT;
|
||||
}
|
||||
return CURRENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAlbumCover(Song song, SongAdapter.ViewHolder holder) {
|
||||
// don't want to load it in this adapter
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Song> dataSet, int position) {
|
||||
this.dataSet = dataSet;
|
||||
current = position;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setCurrent(int current) {
|
||||
this.current = current;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected void setAlpha(SongAdapter.ViewHolder holder, float alpha) {
|
||||
if (holder.image != null) {
|
||||
holder.image.setAlpha(alpha);
|
||||
}
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setAlpha(alpha);
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setAlpha(alpha);
|
||||
}
|
||||
|
||||
if (holder.imageText != null) {
|
||||
holder.imageText.setAlpha(alpha);
|
||||
}
|
||||
|
||||
if (holder.paletteColorContainer != null) {
|
||||
holder.paletteColorContainer.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(ViewHolder holder, int position, int x, int y) {
|
||||
return ViewUtil.hitTest(holder.imageText, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(ViewHolder holder, int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
MusicPlayerRemote.moveSong(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragStarted(int position) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public class ViewHolder extends SongAdapter.ViewHolder implements DraggableItemViewHolder {
|
||||
@DraggableItemStateFlags
|
||||
private int mDragStateFlags;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
if (imageText != null) {
|
||||
imageText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
image.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSongMenuRes() {
|
||||
return R.menu.menu_item_playing_queue_song;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSongMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_remove_from_queue:
|
||||
MusicPlayerRemote.removeFromQueue(getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onSongMenuItemClick(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDragStateFlags(@DraggableItemStateFlags int flags) {
|
||||
mDragStateFlags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DraggableItemStateFlags
|
||||
public int getDragStateFlags() {
|
||||
return mDragStateFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PlaylistSongAdapter extends AbsOffsetSongAdapter {
|
||||
public PlaylistSongAdapter(AppCompatActivity activity, @NonNull List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder, false);
|
||||
setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new PlaylistSongAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final SongAdapter.ViewHolder holder, int position) {
|
||||
if (holder.getItemViewType() == OFFSET_ITEM) {
|
||||
int textColor = ThemeStore.textColorSecondary(activity);
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(MusicUtil.getPlaylistInfoString(activity, dataSet));
|
||||
holder.title.setTextColor(textColor);
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.menu != null) {
|
||||
holder.menu.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.image != null) {
|
||||
final int padding = activity.getResources().getDimensionPixelSize(R.dimen.default_item_margin) / 2;
|
||||
holder.image.setPadding(padding, padding, padding, padding);
|
||||
holder.image.setColorFilter(textColor);
|
||||
holder.image.setImageResource(R.drawable.ic_timer_white_24dp);
|
||||
}
|
||||
|
||||
if (holder.dragView != null) {
|
||||
holder.dragView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.separator != null) {
|
||||
holder.separator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewHolder extends AbsOffsetSongAdapter.ViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSongMenuRes() {
|
||||
return R.menu.menu_item_cannot_delete_single_songs_playlist_song;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSongMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_go_to_album) {
|
||||
Pair[] albumPairs = new Pair[]{Pair.create(image, activity.getString(R.string.transition_album_art))};
|
||||
NavigationUtil.goToAlbum(activity, new Album(dataSet.get(getAdapterPosition() - 1)), albumPairs);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onSongMenuItemClick(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShuffleButtonSongAdapter extends AbsOffsetSongAdapter {
|
||||
|
||||
public ShuffleButtonSongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
super(activity, dataSet, itemLayoutRes, usePalette, cabHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SongAdapter.ViewHolder createViewHolder(View view) {
|
||||
return new ShuffleButtonSongAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final SongAdapter.ViewHolder holder, int position) {
|
||||
if (holder.getItemViewType() == OFFSET_ITEM) {
|
||||
int accentColor = ThemeStore.accentColor(activity);
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(activity.getResources().getString(R.string.action_shuffle_all).toUpperCase());
|
||||
holder.title.setTextColor(accentColor);
|
||||
holder.title.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.menu != null) {
|
||||
holder.menu.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (holder.image != null) {
|
||||
final int padding = activity.getResources().getDimensionPixelSize(R.dimen.default_item_margin) / 2;
|
||||
holder.image.setPadding(padding, padding, padding, padding);
|
||||
holder.image.setColorFilter(accentColor);
|
||||
holder.image.setImageResource(R.drawable.ic_shuffle_white_24dp);
|
||||
}
|
||||
|
||||
if (holder.separator != null) {
|
||||
holder.separator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewHolder extends AbsOffsetSongAdapter.ViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getItemViewType() == OFFSET_ITEM) {
|
||||
MusicPlayerRemote.openAndShuffleQueue(dataSet, true);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onClick(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,276 @@
|
|||
package com.dkanada.gramophone.adapter.song;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.base.AbsMultiSelectAdapter;
|
||||
import com.dkanada.gramophone.adapter.base.MediaEntryViewHolder;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.helper.menu.SongMenuHelper;
|
||||
import com.dkanada.gramophone.helper.menu.SongsMenuHelper;
|
||||
import com.dkanada.gramophone.helper.sort.SortMethod;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SongAdapter extends AbsMultiSelectAdapter<SongAdapter.ViewHolder, Song> implements MaterialCab.Callback, FastScrollRecyclerView.SectionedAdapter {
|
||||
|
||||
protected final AppCompatActivity activity;
|
||||
protected List<Song> dataSet;
|
||||
|
||||
protected int itemLayoutRes;
|
||||
|
||||
protected boolean usePalette;
|
||||
protected boolean showSectionName;
|
||||
|
||||
public SongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) {
|
||||
this(activity, dataSet, itemLayoutRes, usePalette, cabHolder, true);
|
||||
}
|
||||
|
||||
public SongAdapter(AppCompatActivity activity, List<Song> dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder, boolean showSectionName) {
|
||||
super(activity, cabHolder, R.menu.menu_media_selection);
|
||||
|
||||
this.activity = activity;
|
||||
this.dataSet = dataSet;
|
||||
this.itemLayoutRes = itemLayoutRes;
|
||||
this.usePalette = usePalette;
|
||||
this.showSectionName = showSectionName;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void swapDataSet(List<Song> dataSet) {
|
||||
this.dataSet = dataSet;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void usePalette(boolean usePalette) {
|
||||
this.usePalette = usePalette;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<Song> getDataSet() {
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return dataSet.get(position).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false);
|
||||
return createViewHolder(view);
|
||||
}
|
||||
|
||||
protected ViewHolder createViewHolder(View view) {
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
final Song song = dataSet.get(position);
|
||||
|
||||
boolean isChecked = isChecked(song);
|
||||
holder.itemView.setActivated(isChecked);
|
||||
|
||||
if (holder.getAdapterPosition() == getItemCount() - 1) {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (holder.shortSeparator != null) {
|
||||
holder.shortSeparator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (holder.title != null) {
|
||||
holder.title.setText(getSongTitle(song));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setText(getSongText(song));
|
||||
}
|
||||
|
||||
loadAlbumCover(song, holder);
|
||||
}
|
||||
|
||||
private void setColors(int color, ViewHolder holder) {
|
||||
if (holder.paletteColorContainer != null) {
|
||||
holder.paletteColorContainer.setBackgroundColor(color);
|
||||
if (holder.title != null) {
|
||||
holder.title.setTextColor(MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
|
||||
if (holder.text != null) {
|
||||
holder.text.setTextColor(MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadAlbumCover(Song song, final ViewHolder holder) {
|
||||
if (holder.image == null) return;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(activity), song.primary)
|
||||
.generatePalette(activity).build()
|
||||
.into(new CustomPaletteTarget(holder.image) {
|
||||
@Override
|
||||
public void onLoadCleared(Drawable placeholder) {
|
||||
super.onLoadCleared(placeholder);
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
if (usePalette) {
|
||||
setColors(color, holder);
|
||||
} else {
|
||||
setColors(getDefaultFooterColor(), holder);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected String getSongTitle(Song song) {
|
||||
return song.title;
|
||||
}
|
||||
|
||||
protected String getSongText(Song song) {
|
||||
return MusicUtil.getSongInfoString(song);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return dataSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Song getIdentifier(int position) {
|
||||
return dataSet.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(Song song) {
|
||||
return song.title;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull List<Song> selection) {
|
||||
SongsMenuHelper.handleMenuClick(activity, selection, menuItem.getItemId());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSectionName(int position) {
|
||||
if (!showSectionName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable String sectionName = null;
|
||||
switch (PreferenceUtil.getInstance(activity).getSongSortMethod()) {
|
||||
case SortMethod.NAME:
|
||||
sectionName = dataSet.get(position).title;
|
||||
break;
|
||||
case SortMethod.ALBUM:
|
||||
sectionName = dataSet.get(position).albumName;
|
||||
break;
|
||||
case SortMethod.ARTIST:
|
||||
sectionName = dataSet.get(position).artistName;
|
||||
break;
|
||||
case SortMethod.YEAR:
|
||||
return MusicUtil.getYearString(dataSet.get(position).year);
|
||||
case SortMethod.RANDOM:
|
||||
return activity.getResources().getString(R.string.random);
|
||||
}
|
||||
|
||||
return MusicUtil.getSectionName(sectionName);
|
||||
}
|
||||
|
||||
public class ViewHolder extends MediaEntryViewHolder {
|
||||
protected int DEFAULT_MENU_RES = SongMenuHelper.MENU_RES;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
setImageTransitionName(activity.getString(R.string.transition_album_art));
|
||||
|
||||
if (menu == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) {
|
||||
@Override
|
||||
public Song getSong() {
|
||||
return ViewHolder.this.getSong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuRes() {
|
||||
return getSongMenuRes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return onSongMenuItemClick(item) || super.onMenuItemClick(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Song getSong() {
|
||||
return dataSet.get(getAdapterPosition());
|
||||
}
|
||||
|
||||
protected int getSongMenuRes() {
|
||||
return DEFAULT_MENU_RES;
|
||||
}
|
||||
|
||||
protected boolean onSongMenuItemClick(MenuItem item) {
|
||||
if (image != null && image.getVisibility() == View.VISIBLE) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_go_to_album:
|
||||
Pair[] albumPairs = new Pair[]{Pair.create(image, activity.getResources().getString(R.string.transition_album_art))};
|
||||
NavigationUtil.goToAlbum(activity, new Album(getSong()), albumPairs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (isInQuickSelectMode()) {
|
||||
toggleChecked(getAdapterPosition());
|
||||
} else {
|
||||
MusicPlayerRemote.openQueue(dataSet, getAdapterPosition(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
return toggleChecked(getAdapterPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AddToPlaylistDialog extends DialogFragment {
|
||||
|
||||
@NonNull
|
||||
public static AddToPlaylistDialog create(Song song) {
|
||||
List<Song> list = new ArrayList<>();
|
||||
list.add(song);
|
||||
return create(list);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AddToPlaylistDialog create(List<Song> songs) {
|
||||
AddToPlaylistDialog dialog = new AddToPlaylistDialog();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelableArrayList("songs", new ArrayList<>(songs));
|
||||
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
List<Playlist> playlists = new ArrayList<>();
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.action_add_to_playlist)
|
||||
.items(getActivity().getResources().getString(R.string.action_new_playlist))
|
||||
.itemsCallback((materialDialog, view, i, charSequence) -> {
|
||||
final List<Song> songs = getArguments().getParcelableArrayList("songs");
|
||||
if (songs == null) return;
|
||||
|
||||
if (i == 0) {
|
||||
materialDialog.dismiss();
|
||||
CreatePlaylistDialog.create(songs).show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST");
|
||||
} else {
|
||||
materialDialog.dismiss();
|
||||
PlaylistUtil.addItems(songs, playlists.get(i - 1).id);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
QueryUtil.getPlaylists(new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
playlists.addAll((List<Playlist>) media);
|
||||
|
||||
CharSequence[] names = new CharSequence[playlists.size() + 1];
|
||||
names[0] = getActivity().getResources().getString(R.string.action_new_playlist);
|
||||
for (int i = 0; i < playlists.size(); i++) {
|
||||
names[i + 1] = playlists.get(i).name;
|
||||
}
|
||||
|
||||
dialog.setItems(names);
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.text.InputType;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CreatePlaylistDialog extends DialogFragment {
|
||||
|
||||
private static final String SONGS = "songs";
|
||||
|
||||
@NonNull
|
||||
public static CreatePlaylistDialog create() {
|
||||
return create((Song) null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static CreatePlaylistDialog create(@Nullable Song song) {
|
||||
List<Song> list = new ArrayList<>();
|
||||
if (song != null) list.add(song);
|
||||
return create(list);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static CreatePlaylistDialog create(List<Song> songs) {
|
||||
CreatePlaylistDialog dialog = new CreatePlaylistDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelableArrayList(SONGS, new ArrayList<>(songs));
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.action_new_playlist)
|
||||
.positiveText(R.string.create_action)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME | InputType.TYPE_TEXT_FLAG_CAP_WORDS)
|
||||
.input(R.string.playlist_name_empty, 0, false, (materialDialog, charSequence) -> {
|
||||
final String name = charSequence.toString().trim();
|
||||
if (getActivity() == null || getArguments() == null || name.isEmpty()) return;
|
||||
List<Song> songs = getArguments().getParcelableArrayList(SONGS);
|
||||
if (songs != null) {
|
||||
PlaylistUtil.createPlaylist(name, songs);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.text.Html;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DeletePlaylistDialog extends DialogFragment {
|
||||
|
||||
@NonNull
|
||||
public static DeletePlaylistDialog create(Playlist playlist) {
|
||||
List<Playlist> list = new ArrayList<>();
|
||||
list.add(playlist);
|
||||
return create(list);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DeletePlaylistDialog create(List<Playlist> playlists) {
|
||||
DeletePlaylistDialog dialog = new DeletePlaylistDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelableArrayList("playlists", new ArrayList<>(playlists));
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final List<Playlist> playlists = getArguments().getParcelableArrayList("playlists");
|
||||
int title;
|
||||
CharSequence content;
|
||||
// noinspection ConstantConditions
|
||||
if (playlists.size() > 1) {
|
||||
title = R.string.delete_playlists_title;
|
||||
content = Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size()));
|
||||
} else {
|
||||
title = R.string.delete_playlist_title;
|
||||
content = Html.fromHtml(getString(R.string.delete_playlist_x, playlists.get(0).name));
|
||||
}
|
||||
|
||||
return new MaterialDialog.Builder(getActivity())
|
||||
.title(title)
|
||||
.content(content)
|
||||
.positiveText(R.string.delete_action)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive((dialog, which) -> {
|
||||
if (getActivity() == null) return;
|
||||
PlaylistUtil.deletePlaylist(playlists);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.text.Html;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.PlaylistSong;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RemoveFromPlaylistDialog extends DialogFragment {
|
||||
|
||||
@NonNull
|
||||
public static RemoveFromPlaylistDialog create(Song song) {
|
||||
List<Song> list = new ArrayList<>();
|
||||
list.add(song);
|
||||
return create(list);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static RemoveFromPlaylistDialog create(List<Song> songs) {
|
||||
RemoveFromPlaylistDialog dialog = new RemoveFromPlaylistDialog();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelableArrayList("songs", new ArrayList<>(songs));
|
||||
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final List<PlaylistSong> songs = getArguments().getParcelableArrayList("songs");
|
||||
|
||||
int title;
|
||||
CharSequence content;
|
||||
if (songs.size() > 1) {
|
||||
title = R.string.remove_songs_from_playlist_title;
|
||||
content = Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size()));
|
||||
} else {
|
||||
title = R.string.remove_song_from_playlist_title;
|
||||
content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs.get(0).title));
|
||||
}
|
||||
|
||||
return new MaterialDialog.Builder(getActivity())
|
||||
.title(title)
|
||||
.content(content)
|
||||
.positiveText(R.string.remove_action)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onPositive((dialog, which) -> {
|
||||
if (getActivity() == null) return;
|
||||
|
||||
PlaylistSong song = songs.get(0);
|
||||
PlaylistUtil.deleteItems(songs, song.playlistId);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.text.InputType;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
|
||||
public class RenamePlaylistDialog extends DialogFragment {
|
||||
|
||||
private static final String PLAYLIST_ID = "playlist_id";
|
||||
|
||||
@NonNull
|
||||
public static RenamePlaylistDialog create(Playlist playlist) {
|
||||
RenamePlaylistDialog dialog = new RenamePlaylistDialog();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(PLAYLIST_ID, playlist.id);
|
||||
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
String playlistId = getArguments().getString(PLAYLIST_ID);
|
||||
return new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.rename_playlist_title)
|
||||
.positiveText(R.string.rename_action)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME | InputType.TYPE_TEXT_FLAG_CAP_WORDS)
|
||||
.input(getString(R.string.playlist_name_empty), "", false,
|
||||
(materialDialog, charSequence) -> {
|
||||
final String name = charSequence.toString().trim();
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
String id = getArguments().getString(PLAYLIST_ID);
|
||||
PlaylistUtil.renamePlaylist(id, name);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Dialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.afollestad.materialdialogs.internal.ThemeSingleton;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.triggertrap.seekarc.SeekArc;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SleepTimerDialog extends DialogFragment {
|
||||
@BindView(R.id.seek_arc)
|
||||
SeekArc seekArc;
|
||||
|
||||
@BindView(R.id.timer_display)
|
||||
TextView timerDisplay;
|
||||
|
||||
@BindView(R.id.should_finish_last_song)
|
||||
CheckBox shouldFinishLastSong;
|
||||
|
||||
private int seekArcProgress;
|
||||
private MaterialDialog materialDialog;
|
||||
private TimerUpdater timerUpdater;
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
timerUpdater.cancel();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
timerUpdater = new TimerUpdater();
|
||||
materialDialog = new MaterialDialog.Builder(getActivity())
|
||||
.title(getActivity().getResources().getString(R.string.action_sleep_timer))
|
||||
.positiveText(R.string.action_set)
|
||||
.onPositive((dialog, which) -> {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreferenceUtil.getInstance(getActivity()).setSleepTimerFinishMusic(shouldFinishLastSong.isChecked());
|
||||
|
||||
final int minutes = seekArcProgress;
|
||||
|
||||
PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
final long nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + minutes * 60 * 1000;
|
||||
PreferenceUtil.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime);
|
||||
AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi);
|
||||
|
||||
Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_set, minutes), Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
.onNeutral((dialog, which) -> {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PendingIntent previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE);
|
||||
if (previous != null) {
|
||||
AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
|
||||
am.cancel(previous);
|
||||
previous.cancel();
|
||||
Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
MusicService musicService = MusicPlayerRemote.musicService;
|
||||
if (musicService != null && musicService.pendingQuit) {
|
||||
musicService.pendingQuit = false;
|
||||
Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.showListener(dialog -> {
|
||||
if (makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) != null) {
|
||||
timerUpdater.start();
|
||||
}
|
||||
})
|
||||
.customView(R.layout.dialog_sleep_timer, false)
|
||||
.build();
|
||||
|
||||
if (getActivity() == null || materialDialog.getCustomView() == null) {
|
||||
return materialDialog;
|
||||
}
|
||||
|
||||
ButterKnife.bind(this, materialDialog.getCustomView());
|
||||
|
||||
boolean finishMusic = PreferenceUtil.getInstance(getActivity()).getSleepTimerFinishMusic();
|
||||
shouldFinishLastSong.setChecked(finishMusic);
|
||||
|
||||
seekArc.setProgressColor(ThemeSingleton.get().positiveColor.getDefaultColor());
|
||||
seekArc.setThumbColor(ThemeSingleton.get().positiveColor.getDefaultColor());
|
||||
|
||||
seekArc.post(() -> {
|
||||
int width = seekArc.getWidth();
|
||||
int height = seekArc.getHeight();
|
||||
int small = Math.min(width, height);
|
||||
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(seekArc.getLayoutParams());
|
||||
layoutParams.height = small;
|
||||
seekArc.setLayoutParams(layoutParams);
|
||||
});
|
||||
|
||||
seekArcProgress = PreferenceUtil.getInstance(getActivity()).getLastSleepTimerValue();
|
||||
updateTimeDisplayTime();
|
||||
seekArc.setProgress(seekArcProgress);
|
||||
|
||||
seekArc.setOnSeekArcChangeListener(new SeekArc.OnSeekArcChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(@NonNull SeekArc seekArc, int i, boolean b) {
|
||||
if (i < 1) {
|
||||
seekArc.setProgress(1);
|
||||
return;
|
||||
}
|
||||
|
||||
seekArcProgress = i;
|
||||
updateTimeDisplayTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekArc seekArc) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekArc seekArc) {
|
||||
PreferenceUtil.getInstance(getActivity()).setLastSleepTimerValue(seekArcProgress);
|
||||
}
|
||||
});
|
||||
|
||||
return materialDialog;
|
||||
}
|
||||
|
||||
private void updateTimeDisplayTime() {
|
||||
timerDisplay.setText(seekArcProgress + " min");
|
||||
}
|
||||
|
||||
private PendingIntent makeTimerPendingIntent(int flag) {
|
||||
return PendingIntent.getService(getActivity(), 0, makeTimerIntent(), flag);
|
||||
}
|
||||
|
||||
private Intent makeTimerIntent() {
|
||||
Intent intent = new Intent(getActivity(), MusicService.class);
|
||||
if (shouldFinishLastSong.isChecked()) {
|
||||
return intent.setAction(MusicService.ACTION_PENDING_QUIT);
|
||||
}
|
||||
|
||||
return intent.setAction(MusicService.ACTION_QUIT);
|
||||
}
|
||||
|
||||
private void updateCancelButton() {
|
||||
MusicService musicService = MusicPlayerRemote.musicService;
|
||||
if (musicService != null && musicService.pendingQuit) {
|
||||
materialDialog.setActionButton(DialogAction.NEUTRAL, materialDialog.getContext().getString(R.string.cancel_current_timer));
|
||||
} else {
|
||||
materialDialog.setActionButton(DialogAction.NEUTRAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class TimerUpdater extends CountDownTimer {
|
||||
public TimerUpdater() {
|
||||
super(PreferenceUtil.getInstance(getActivity()).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(), 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
materialDialog.setActionButton(DialogAction.NEUTRAL, materialDialog.getContext().getString(R.string.cancel_current_timer) + " (" + MusicUtil.getReadableDurationString(millisUntilFinished) + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
updateCancelButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
|
||||
public class SongDetailDialog extends DialogFragment {
|
||||
@NonNull
|
||||
public static SongDetailDialog create(Song song) {
|
||||
SongDetailDialog dialog = new SongDetailDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable("song", song);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private static Spanned makeTextWithTitle(@NonNull Context context, int titleResId, String text) {
|
||||
return Html.fromHtml("<b>" + context.getResources().getString(titleResId) + ": " + "</b>" + text);
|
||||
}
|
||||
|
||||
private static String getFileSizeString(long sizeInBytes) {
|
||||
long fileSizeInKB = sizeInBytes / 1024;
|
||||
long fileSizeInMB = fileSizeInKB / 1024;
|
||||
return fileSizeInMB + " MB";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity context = getActivity();
|
||||
final Song song = getArguments().getParcelable("song");
|
||||
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(context)
|
||||
.customView(R.layout.dialog_file_details, true)
|
||||
.title(context.getResources().getString(R.string.label_details))
|
||||
.positiveText(android.R.string.ok)
|
||||
.build();
|
||||
|
||||
View dialogView = dialog.getCustomView();
|
||||
final TextView fileName = dialogView.findViewById(R.id.file_name);
|
||||
final TextView filePath = dialogView.findViewById(R.id.file_path);
|
||||
final TextView fileSize = dialogView.findViewById(R.id.file_size);
|
||||
final TextView fileFormat = dialogView.findViewById(R.id.file_format);
|
||||
final TextView trackLength = dialogView.findViewById(R.id.track_length);
|
||||
final TextView bitRate = dialogView.findViewById(R.id.bitrate);
|
||||
final TextView samplingRate = dialogView.findViewById(R.id.sampling_rate);
|
||||
|
||||
fileName.setText(makeTextWithTitle(context, R.string.label_file_name, "-"));
|
||||
filePath.setText(makeTextWithTitle(context, R.string.label_file_path, "-"));
|
||||
fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, "-"));
|
||||
fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, "-"));
|
||||
trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, "-"));
|
||||
bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, "-"));
|
||||
samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, "-"));
|
||||
|
||||
if (song != null) {
|
||||
fileName.setText(makeTextWithTitle(context, R.string.label_file_name, song.title));
|
||||
trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, MusicUtil.getReadableDurationString(song.duration)));
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.dkanada.gramophone.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
|
||||
public class SongShareDialog extends DialogFragment {
|
||||
@NonNull
|
||||
public static SongShareDialog create(final Song song) {
|
||||
final SongShareDialog dialog = new SongShareDialog();
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable("song", song);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Song song = getArguments().getParcelable("song");
|
||||
final String currentlyListening = getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName);
|
||||
return new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.what_do_you_want_to_share)
|
||||
.items(getString(R.string.the_audio_file), "\u201C" + currentlyListening + "\u201D")
|
||||
.itemsCallback((materialDialog, view, i, charSequence) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
startActivity(Intent.createChooser(MusicUtil.createShareSongFileIntent(song, getContext()), null));
|
||||
break;
|
||||
case 1:
|
||||
getActivity().startActivity(
|
||||
Intent.createChooser(
|
||||
new Intent()
|
||||
.setAction(Intent.ACTION_SEND)
|
||||
.putExtra(Intent.EXTRA_TEXT, currentlyListening)
|
||||
.setType("text/plain"),
|
||||
null
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.dkanada.gramophone.glide;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.renderscript.Allocation;
|
||||
import android.renderscript.Element;
|
||||
import android.renderscript.RSRuntimeException;
|
||||
import android.renderscript.RenderScript;
|
||||
import android.renderscript.ScriptIntrinsicBlur;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.dkanada.gramophone.BuildConfig;
|
||||
import com.dkanada.gramophone.helper.StackBlur;
|
||||
import com.dkanada.gramophone.util.ImageUtil;
|
||||
|
||||
public class BlurTransformation extends BitmapTransformation {
|
||||
public static final float DEFAULT_BLUR_RADIUS = 5f;
|
||||
|
||||
private Context context;
|
||||
private float blurRadius;
|
||||
private int sampling;
|
||||
|
||||
private void init(Builder builder) {
|
||||
this.context = builder.context;
|
||||
this.blurRadius = builder.blurRadius;
|
||||
this.sampling = builder.sampling;
|
||||
}
|
||||
|
||||
private BlurTransformation(Builder builder) {
|
||||
super(builder.context);
|
||||
init(builder);
|
||||
}
|
||||
|
||||
private BlurTransformation(Builder builder, BitmapPool bitmapPool) {
|
||||
super(bitmapPool);
|
||||
init(builder);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Context context;
|
||||
private BitmapPool bitmapPool;
|
||||
private float blurRadius = DEFAULT_BLUR_RADIUS;
|
||||
private int sampling;
|
||||
|
||||
public Builder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param blurRadius The radius to use. Must be between 0 and 25. Default is 5.
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder blurRadius(@FloatRange(from = 0.0f, to = 25.0f) float blurRadius) {
|
||||
this.blurRadius = blurRadius;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sampling The inSampleSize to use. Must be a power of 2, or 1 for no down sampling or 0 for auto detect sampling. Default is 0.
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder sampling(int sampling) {
|
||||
this.sampling = sampling;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmapPool The BitmapPool to use.
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder bitmapPool(BitmapPool bitmapPool) {
|
||||
this.bitmapPool = bitmapPool;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlurTransformation build() {
|
||||
if (bitmapPool != null) {
|
||||
return new BlurTransformation(this, bitmapPool);
|
||||
}
|
||||
return new BlurTransformation(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
|
||||
int sampling;
|
||||
if (this.sampling == 0) {
|
||||
sampling = ImageUtil.calculateInSampleSize(toTransform.getWidth(), toTransform.getHeight(), 100);
|
||||
} else {
|
||||
sampling = this.sampling;
|
||||
}
|
||||
|
||||
int width = toTransform.getWidth();
|
||||
int height = toTransform.getHeight();
|
||||
int scaledWidth = width / sampling;
|
||||
int scaledHeight = height / sampling;
|
||||
|
||||
Bitmap out = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||
if (out == null) {
|
||||
out = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
Canvas canvas = new Canvas(out);
|
||||
canvas.scale(1 / (float) sampling, 1 / (float) sampling);
|
||||
Paint paint = new Paint();
|
||||
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
|
||||
canvas.drawBitmap(toTransform, 0, 0, paint);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
try {
|
||||
final RenderScript rs = RenderScript.create(context.getApplicationContext());
|
||||
final Allocation input = Allocation.createFromBitmap(rs, out, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
|
||||
final Allocation output = Allocation.createTyped(rs, input.getType());
|
||||
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
|
||||
|
||||
script.setRadius(blurRadius);
|
||||
script.setInput(input);
|
||||
script.forEach(output);
|
||||
|
||||
output.copyTo(out);
|
||||
|
||||
rs.destroy();
|
||||
|
||||
return out;
|
||||
} catch (RSRuntimeException e) {
|
||||
// on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library
|
||||
if (BuildConfig.DEBUG) e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return StackBlur.blur(out, blurRadius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "BlurTransformation(radius=" + blurRadius + ", sampling=" + sampling + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.dkanada.gramophone.glide;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.BitmapRequestBuilder;
|
||||
import com.bumptech.glide.DrawableRequestBuilder;
|
||||
import com.bumptech.glide.DrawableTypeRequest;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.signature.MediaStoreSignature;
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteTranscoder;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.ImageOptions;
|
||||
import org.jellyfin.apiclient.model.entities.ImageType;
|
||||
|
||||
public class CustomGlideRequest {
|
||||
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
|
||||
|
||||
public static final int DEFAULT_IMAGE = R.drawable.default_album_art;
|
||||
public static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
|
||||
|
||||
public static class Builder {
|
||||
final RequestManager requestManager;
|
||||
final String item;
|
||||
|
||||
public static Builder from(@NonNull RequestManager requestManager, String item) {
|
||||
return new Builder(requestManager, item);
|
||||
}
|
||||
|
||||
private Builder(@NonNull RequestManager requestManager, String item) {
|
||||
this.requestManager = requestManager;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public PaletteBuilder generatePalette(Context context) {
|
||||
return new PaletteBuilder(this, context);
|
||||
}
|
||||
|
||||
public BitmapBuilder asBitmap() {
|
||||
return new BitmapBuilder(this);
|
||||
}
|
||||
|
||||
public DrawableRequestBuilder<GlideDrawable> build() {
|
||||
// noinspection unchecked
|
||||
return createBaseRequest(requestManager, item)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.placeholder(DEFAULT_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(item));
|
||||
}
|
||||
}
|
||||
|
||||
public static class BitmapBuilder {
|
||||
private final Builder builder;
|
||||
|
||||
public BitmapBuilder(Builder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, Bitmap> build() {
|
||||
// noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.item)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.placeholder(DEFAULT_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(builder.item));
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaletteBuilder {
|
||||
final Context context;
|
||||
private final Builder builder;
|
||||
|
||||
public PaletteBuilder(Builder builder, Context context) {
|
||||
this.builder = builder;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() {
|
||||
// noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.item)
|
||||
.asBitmap()
|
||||
.transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.encoder(new BitmapEncoder(Bitmap.CompressFormat.PNG, 100))
|
||||
.placeholder(DEFAULT_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(builder.item));
|
||||
}
|
||||
}
|
||||
|
||||
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, String item) {
|
||||
if (item == null) {
|
||||
return requestManager.load(R.drawable.default_album_art);
|
||||
}
|
||||
|
||||
ImageOptions options = new ImageOptions();
|
||||
options.setImageType(ImageType.Primary);
|
||||
options.setMaxHeight(800);
|
||||
|
||||
String url = App.getApiClient().GetImageUrl(item, options);
|
||||
return requestManager.load(url);
|
||||
}
|
||||
|
||||
public static Key createSignature(String item) {
|
||||
return new MediaStoreSignature("image/jpeg", item != null ? item.hashCode() : 0, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.dkanada.gramophone.glide;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.kabouzeid.appthemehelper.util.ATHUtil;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteTarget;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
|
||||
public abstract class CustomPaletteTarget extends BitmapPaletteTarget {
|
||||
public CustomPaletteTarget(ImageView view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
super.onLoadFailed(e, errorDrawable);
|
||||
onColorReady(getDefaultFooterColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation<? super BitmapPaletteWrapper> glideAnimation) {
|
||||
super.onResourceReady(resource, glideAnimation);
|
||||
onColorReady(ThemeUtil.getColor(resource.getPalette(), getDefaultFooterColor()));
|
||||
}
|
||||
|
||||
protected int getDefaultFooterColor() {
|
||||
return ATHUtil.resolveColor(getView().getContext(), R.attr.defaultFooterColor);
|
||||
}
|
||||
|
||||
protected int getAlbumArtistFooterColor() {
|
||||
return ATHUtil.resolveColor(getView().getContext(), R.attr.cardBackgroundColor);
|
||||
}
|
||||
|
||||
public abstract void onColorReady(int color);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.dkanada.gramophone.glide.palette;
|
||||
|
||||
import com.bumptech.glide.load.engine.Resource;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.util.Util;
|
||||
|
||||
public class BitmapPaletteResource implements Resource<BitmapPaletteWrapper> {
|
||||
|
||||
private final BitmapPaletteWrapper bitmapPaletteWrapper;
|
||||
private final BitmapPool bitmapPool;
|
||||
|
||||
public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) {
|
||||
this.bitmapPaletteWrapper = bitmapPaletteWrapper;
|
||||
this.bitmapPool = bitmapPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapPaletteWrapper get() {
|
||||
return bitmapPaletteWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) {
|
||||
bitmapPaletteWrapper.getBitmap().recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.dkanada.gramophone.glide.palette;
|
||||
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||
|
||||
public class BitmapPaletteTarget extends ImageViewTarget<BitmapPaletteWrapper> {
|
||||
public BitmapPaletteTarget(ImageView view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) {
|
||||
view.setImageBitmap(bitmapPaletteWrapper.getBitmap());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.dkanada.gramophone.glide.palette;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.Resource;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
|
||||
public class BitmapPaletteTranscoder implements ResourceTranscoder<Bitmap, BitmapPaletteWrapper> {
|
||||
private final BitmapPool bitmapPool;
|
||||
|
||||
public BitmapPaletteTranscoder(Context context) {
|
||||
this(Glide.get(context).getBitmapPool());
|
||||
}
|
||||
|
||||
public BitmapPaletteTranscoder(BitmapPool bitmapPool) {
|
||||
this.bitmapPool = bitmapPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource<BitmapPaletteWrapper> transcode(Resource<Bitmap> bitmapResource) {
|
||||
Bitmap bitmap = bitmapResource.get();
|
||||
BitmapPaletteWrapper bitmapPaletteWrapper = new BitmapPaletteWrapper(bitmap, ThemeUtil.generatePalette(bitmap));
|
||||
return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "BitmapPaletteTranscoder.com.kabouzeid.gramophone.glide.palette";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.dkanada.gramophone.glide.palette;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
public class BitmapPaletteWrapper {
|
||||
private final Bitmap mBitmap;
|
||||
private final Palette mPalette;
|
||||
|
||||
public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) {
|
||||
mBitmap = bitmap;
|
||||
mPalette = palette;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
public Palette getPalette() {
|
||||
return mPalette;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
|
||||
public class HorizontalAdapterHelper {
|
||||
public static final int LAYOUT_RES = R.layout.item_grid_card_horizontal;
|
||||
|
||||
public static final int TYPE_FIRST = 1;
|
||||
public static final int TYPE_MIDDLE = 2;
|
||||
public static final int TYPE_LAST = 3;
|
||||
|
||||
public static void applyMarginToLayoutParams(Context context, ViewGroup.MarginLayoutParams layoutParams, int viewType) {
|
||||
int listMargin = context.getResources().getDimensionPixelSize(R.dimen.default_item_margin);
|
||||
if (viewType == TYPE_FIRST) {
|
||||
layoutParams.leftMargin = listMargin;
|
||||
} else if (viewType == TYPE_LAST) {
|
||||
layoutParams.rightMargin = listMargin;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getItemViewtype(int position, int itemCount) {
|
||||
if (position == 0) {
|
||||
return TYPE_FIRST;
|
||||
} else if (position == itemCount - 1) {
|
||||
return TYPE_LAST;
|
||||
} else return TYPE_MIDDLE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class MusicPlayerRemote {
|
||||
public static MusicService musicService;
|
||||
|
||||
private static final WeakHashMap<Context, ServiceBinder> mConnectionMap = new WeakHashMap<>();
|
||||
|
||||
public static ServiceToken bindToService(@NonNull final Context context,
|
||||
final ServiceConnection callback) {
|
||||
Activity realActivity = ((Activity) context).getParent();
|
||||
if (realActivity == null) {
|
||||
realActivity = (Activity) context;
|
||||
}
|
||||
|
||||
final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
|
||||
contextWrapper.startService(new Intent(contextWrapper, MusicService.class));
|
||||
|
||||
final ServiceBinder binder = new ServiceBinder(callback);
|
||||
|
||||
if (contextWrapper.bindService(new Intent().setClass(contextWrapper, MusicService.class), binder, Context.BIND_AUTO_CREATE)) {
|
||||
mConnectionMap.put(contextWrapper, binder);
|
||||
return new ServiceToken(contextWrapper);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void unbindFromService(@Nullable final ServiceToken token) {
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
final ContextWrapper mContextWrapper = token.mWrappedContext;
|
||||
final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
|
||||
if (mBinder == null) {
|
||||
return;
|
||||
}
|
||||
mContextWrapper.unbindService(mBinder);
|
||||
if (mConnectionMap.isEmpty()) {
|
||||
musicService = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ServiceBinder implements ServiceConnection {
|
||||
private final ServiceConnection mCallback;
|
||||
|
||||
public ServiceBinder(final ServiceConnection callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(final ComponentName className, final IBinder service) {
|
||||
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
|
||||
musicService = binder.getService();
|
||||
if (mCallback != null) {
|
||||
mCallback.onServiceConnected(className, service);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(final ComponentName className) {
|
||||
if (mCallback != null) {
|
||||
mCallback.onServiceDisconnected(className);
|
||||
}
|
||||
musicService = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ServiceToken {
|
||||
public ContextWrapper mWrappedContext;
|
||||
|
||||
public ServiceToken(final ContextWrapper context) {
|
||||
mWrappedContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
public static void playSongAt(final int position) {
|
||||
if (musicService != null) {
|
||||
musicService.playSongAt(position);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPosition(final int position) {
|
||||
if (musicService != null) {
|
||||
musicService.setPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
public static void pauseSong() {
|
||||
if (musicService != null) {
|
||||
musicService.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public static void playNextSong() {
|
||||
if (musicService != null) {
|
||||
musicService.playNextSong(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void playPreviousSong() {
|
||||
if (musicService != null) {
|
||||
musicService.playPreviousSong(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void back() {
|
||||
if (musicService != null) {
|
||||
musicService.back(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPlaying() {
|
||||
return musicService != null && musicService.isPlaying();
|
||||
}
|
||||
|
||||
public static void resumePlaying() {
|
||||
if (musicService != null) {
|
||||
musicService.play();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openQueue(final List<Song> queue, final int startPosition, final boolean startPlaying) {
|
||||
if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
|
||||
musicService.openQueue(queue, startPosition, startPlaying);
|
||||
if (!PreferenceUtil.getInstance(musicService).getRememberShuffle()){
|
||||
setShuffleMode(MusicService.SHUFFLE_MODE_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void openAndShuffleQueue(final List<Song> queue, boolean startPlaying) {
|
||||
int startPosition = 0;
|
||||
if (!queue.isEmpty()) {
|
||||
startPosition = new Random().nextInt(queue.size());
|
||||
}
|
||||
|
||||
if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
|
||||
openQueue(queue, startPosition, startPlaying);
|
||||
setShuffleMode(MusicService.SHUFFLE_MODE_SHUFFLE);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tryToHandleOpenPlayingQueue(final List<Song> queue, final int startPosition, final boolean startPlaying) {
|
||||
if (getPlayingQueue() == queue) {
|
||||
if (startPlaying) {
|
||||
playSongAt(startPosition);
|
||||
} else {
|
||||
setPosition(startPosition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Song getCurrentSong() {
|
||||
if (musicService != null) {
|
||||
return musicService.getCurrentSong();
|
||||
}
|
||||
return Song.EMPTY_SONG;
|
||||
}
|
||||
|
||||
public static int getPosition() {
|
||||
if (musicService != null) {
|
||||
return musicService.getPosition();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static List<Song> getPlayingQueue() {
|
||||
if (musicService != null) {
|
||||
return musicService.getPlayingQueue();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static int getSongProgressMillis() {
|
||||
if (musicService != null) {
|
||||
return musicService.getSongProgressMillis();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int getSongDurationMillis() {
|
||||
if (musicService != null) {
|
||||
return musicService.getSongDurationMillis();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static long getQueueDurationMillis(int position) {
|
||||
if (musicService != null) {
|
||||
return musicService.getQueueDurationMillis(position);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int seekTo(int millis) {
|
||||
if (musicService != null) {
|
||||
return musicService.seek(millis);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int getRepeatMode() {
|
||||
if (musicService != null) {
|
||||
return musicService.getRepeatMode();
|
||||
}
|
||||
return MusicService.REPEAT_MODE_NONE;
|
||||
}
|
||||
|
||||
public static int getShuffleMode() {
|
||||
if (musicService != null) {
|
||||
return musicService.getShuffleMode();
|
||||
}
|
||||
return MusicService.SHUFFLE_MODE_NONE;
|
||||
}
|
||||
|
||||
public static boolean cycleRepeatMode() {
|
||||
if (musicService != null) {
|
||||
musicService.cycleRepeatMode();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean toggleShuffleMode() {
|
||||
if (musicService != null) {
|
||||
musicService.toggleShuffle();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean setShuffleMode(final int shuffleMode) {
|
||||
if (musicService != null) {
|
||||
musicService.setShuffleMode(shuffleMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean playNext(Song song) {
|
||||
if (musicService != null) {
|
||||
if (getPlayingQueue().size() > 0) {
|
||||
musicService.addSong(getPosition() + 1, song);
|
||||
} else {
|
||||
List<Song> queue = new ArrayList<>();
|
||||
queue.add(song);
|
||||
openQueue(queue, 0, false);
|
||||
}
|
||||
|
||||
Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_queue), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean playNext(@NonNull List<Song> songs) {
|
||||
if (musicService != null) {
|
||||
if (getPlayingQueue().size() > 0) {
|
||||
musicService.addSongs(getPosition() + 1, songs);
|
||||
} else {
|
||||
openQueue(songs, 0, false);
|
||||
}
|
||||
|
||||
final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_queue) : musicService.getResources().getString(R.string.added_x_titles_to_queue, songs.size());
|
||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean enqueue(Song song) {
|
||||
if (musicService != null) {
|
||||
if (getPlayingQueue().size() > 0) {
|
||||
musicService.addSong(song);
|
||||
} else {
|
||||
List<Song> queue = new ArrayList<>();
|
||||
queue.add(song);
|
||||
openQueue(queue, 0, false);
|
||||
}
|
||||
|
||||
Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_queue), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean enqueue(@NonNull List<Song> songs) {
|
||||
if (musicService != null) {
|
||||
if (getPlayingQueue().size() > 0) {
|
||||
musicService.addSongs(songs);
|
||||
} else {
|
||||
openQueue(songs, 0, false);
|
||||
}
|
||||
|
||||
final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_queue) : musicService.getResources().getString(R.string.added_x_titles_to_queue, songs.size());
|
||||
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean removeFromQueue(@NonNull Song song) {
|
||||
if (musicService != null) {
|
||||
musicService.removeSong(song);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean removeFromQueue(int position) {
|
||||
if (musicService != null && position >= 0 && position < getPlayingQueue().size()) {
|
||||
musicService.removeSong(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean moveSong(int from, int to) {
|
||||
if (musicService != null && from >= 0 && to >= 0 && from < getPlayingQueue().size() && to < getPlayingQueue().size()) {
|
||||
musicService.moveSong(from, to);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean clearQueue() {
|
||||
if (musicService != null) {
|
||||
musicService.clearQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MusicProgressViewUpdateHelper extends Handler {
|
||||
private static final int CMD_REFRESH_PROGRESS_VIEWS = 1;
|
||||
|
||||
private static final int MIN_INTERVAL = 20;
|
||||
private static final int UPDATE_INTERVAL_PLAYING = 1000;
|
||||
private static final int UPDATE_INTERVAL_PAUSED = 500;
|
||||
|
||||
private Callback callback;
|
||||
private int intervalPlaying;
|
||||
private int intervalPaused;
|
||||
|
||||
public void start() {
|
||||
queueNextRefresh(1);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
|
||||
}
|
||||
|
||||
public MusicProgressViewUpdateHelper(Callback callback) {
|
||||
this.callback = callback;
|
||||
this.intervalPlaying = UPDATE_INTERVAL_PLAYING;
|
||||
this.intervalPaused = UPDATE_INTERVAL_PAUSED;
|
||||
}
|
||||
|
||||
public MusicProgressViewUpdateHelper(Callback callback, int intervalPlaying, int intervalPaused) {
|
||||
this.callback = callback;
|
||||
this.intervalPlaying = intervalPlaying;
|
||||
this.intervalPaused = intervalPaused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
super.handleMessage(msg);
|
||||
if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) {
|
||||
queueNextRefresh(refreshProgressViews());
|
||||
}
|
||||
}
|
||||
|
||||
private int refreshProgressViews() {
|
||||
final int progressMillis = MusicPlayerRemote.getSongProgressMillis();
|
||||
final int totalMillis = MusicPlayerRemote.getSongDurationMillis();
|
||||
|
||||
callback.onUpdateProgressViews(progressMillis, totalMillis);
|
||||
|
||||
if (!MusicPlayerRemote.isPlaying()) {
|
||||
return intervalPaused;
|
||||
}
|
||||
|
||||
final int remainingMillis = intervalPlaying - progressMillis % intervalPlaying;
|
||||
|
||||
return Math.max(MIN_INTERVAL, remainingMillis);
|
||||
}
|
||||
|
||||
private void queueNextRefresh(final long delay) {
|
||||
final Message message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS);
|
||||
removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
|
||||
sendMessageDelayed(message, delay);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onUpdateProgressViews(int progress, int total);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public class PlayPauseButtonOnClickHandler implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (MusicPlayerRemote.isPlaying()) {
|
||||
MusicPlayerRemote.pauseSong();
|
||||
} else {
|
||||
MusicPlayerRemote.resumePlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ShuffleHelper {
|
||||
public static void makeShuffleList(@NonNull List<Song> listToShuffle, final int current) {
|
||||
if (listToShuffle.isEmpty()) return;
|
||||
|
||||
if (current >= 0) {
|
||||
Song song = listToShuffle.remove(current);
|
||||
Collections.shuffle(listToShuffle);
|
||||
listToShuffle.add(0, song);
|
||||
} else {
|
||||
Collections.shuffle(listToShuffle);
|
||||
}
|
||||
}
|
||||
}
|
||||
332
app/src/main/java/com/dkanada/gramophone/helper/StackBlur.java
Normal file
332
app/src/main/java/com/dkanada/gramophone/helper/StackBlur.java
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Blur using Java code.
|
||||
* <p/>
|
||||
* This is a compromise between Gaussian Blur and Box blur
|
||||
* It creates much better looking blurs than Box Blur, but is
|
||||
* 7x faster than my Gaussian Blur implementation.
|
||||
* <p/>
|
||||
* I called it Stack Blur because this describes best how this
|
||||
* filter works internally: it creates a kind of moving stack
|
||||
* of colors whilst scanning through the image. Thereby it
|
||||
* just has to add one new block of color to the right side
|
||||
* of the stack and remove the leftmost color. The remaining
|
||||
* colors on the topmost layer of the stack are either added on
|
||||
* or reduced by one, depending on if they are on the right or
|
||||
* on the left side of the stack.
|
||||
*
|
||||
* @author Enrique López Mañas <eenriquelopez@gmail.com>
|
||||
* http://www.neo-tech.es
|
||||
* <p/>
|
||||
* Author of the original algorithm: Mario Klingemann <mario.quasimondo.com>
|
||||
* <p/>
|
||||
* Based heavily on http://vitiy.info/Code/stackblur.cpp
|
||||
* See http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/
|
||||
* @copyright: Enrique López Mañas
|
||||
* @license: Apache License 2.0
|
||||
*/
|
||||
public class StackBlur {
|
||||
|
||||
static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors();
|
||||
static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS);
|
||||
|
||||
private static final short[] stackblur_mul = {
|
||||
512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
|
||||
454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
|
||||
482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
|
||||
437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
|
||||
497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
|
||||
320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
|
||||
446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
|
||||
329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
|
||||
505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
|
||||
399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
|
||||
324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
|
||||
268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
|
||||
451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
|
||||
385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
|
||||
332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
|
||||
289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259
|
||||
};
|
||||
|
||||
private static final byte[] stackblur_shr = {
|
||||
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
|
||||
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
|
||||
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
|
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
|
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
|
||||
};
|
||||
|
||||
public static Bitmap blur(Bitmap original, float radius) {
|
||||
int w = original.getWidth();
|
||||
int h = original.getHeight();
|
||||
int[] currentPixels = new int[w * h];
|
||||
original.getPixels(currentPixels, 0, w, 0, 0, w, h);
|
||||
int cores = EXECUTOR_THREADS;
|
||||
|
||||
List<BlurTask> horizontal = new ArrayList<>(cores);
|
||||
List<BlurTask> vertical = new ArrayList<>(cores);
|
||||
for (int i = 0; i < cores; i++) {
|
||||
horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1));
|
||||
vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2));
|
||||
}
|
||||
|
||||
try {
|
||||
EXECUTOR.invokeAll(horizontal);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
EXECUTOR.invokeAll(vertical);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
private static void blurIteration(int[] src, int w, int h, int radius, int cores, int core, int step) {
|
||||
int x, y, xp, yp, i;
|
||||
int sp;
|
||||
int stack_start;
|
||||
int stack_i;
|
||||
|
||||
int src_i;
|
||||
int dst_i;
|
||||
|
||||
long sum_r, sum_g, sum_b,
|
||||
sum_in_r, sum_in_g, sum_in_b,
|
||||
sum_out_r, sum_out_g, sum_out_b;
|
||||
|
||||
int wm = w - 1;
|
||||
int hm = h - 1;
|
||||
int div = (radius * 2) + 1;
|
||||
int mul_sum = stackblur_mul[radius];
|
||||
byte shr_sum = stackblur_shr[radius];
|
||||
int[] stack = new int[div];
|
||||
|
||||
if (step == 1) {
|
||||
int minY = core * h / cores;
|
||||
int maxY = (core + 1) * h / cores;
|
||||
|
||||
for (y = minY; y < maxY; y++) {
|
||||
sum_r = sum_g = sum_b =
|
||||
sum_in_r = sum_in_g = sum_in_b =
|
||||
sum_out_r = sum_out_g = sum_out_b = 0;
|
||||
|
||||
src_i = w * y; // start of line (0,y)
|
||||
|
||||
for (i = 0; i <= radius; i++) {
|
||||
stack_i = i;
|
||||
stack[stack_i] = src[src_i];
|
||||
sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
|
||||
sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
|
||||
sum_b += (src[src_i] & 0xff) * (i + 1);
|
||||
sum_out_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_out_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_out_b += (src[src_i] & 0xff);
|
||||
}
|
||||
|
||||
|
||||
for (i = 1; i <= radius; i++) {
|
||||
if (i <= wm) src_i += 1;
|
||||
stack_i = i + radius;
|
||||
stack[stack_i] = src[src_i];
|
||||
sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
|
||||
sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
|
||||
sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
|
||||
sum_in_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_in_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_in_b += (src[src_i] & 0xff);
|
||||
}
|
||||
|
||||
|
||||
sp = radius;
|
||||
xp = radius;
|
||||
if (xp > wm) xp = wm;
|
||||
src_i = xp + y * w; // img.pix_ptr(xp, y);
|
||||
dst_i = y * w; // img.pix_ptr(0, y);
|
||||
for (x = 0; x < w; x++) {
|
||||
src[dst_i] = (int)
|
||||
((src[dst_i] & 0xff000000) |
|
||||
((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
|
||||
((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
|
||||
((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
|
||||
dst_i += 1;
|
||||
|
||||
sum_r -= sum_out_r;
|
||||
sum_g -= sum_out_g;
|
||||
sum_b -= sum_out_b;
|
||||
|
||||
stack_start = sp + div - radius;
|
||||
if (stack_start >= div) stack_start -= div;
|
||||
stack_i = stack_start;
|
||||
|
||||
sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_out_b -= (stack[stack_i] & 0xff);
|
||||
|
||||
if (xp < wm) {
|
||||
src_i += 1;
|
||||
++xp;
|
||||
}
|
||||
|
||||
stack[stack_i] = src[src_i];
|
||||
|
||||
sum_in_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_in_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_in_b += (src[src_i] & 0xff);
|
||||
sum_r += sum_in_r;
|
||||
sum_g += sum_in_g;
|
||||
sum_b += sum_in_b;
|
||||
|
||||
++sp;
|
||||
if (sp >= div) sp = 0;
|
||||
stack_i = sp;
|
||||
|
||||
sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_out_b += (stack[stack_i] & 0xff);
|
||||
sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_in_b -= (stack[stack_i] & 0xff);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// step 2
|
||||
else if (step == 2) {
|
||||
int minX = core * w / cores;
|
||||
int maxX = (core + 1) * w / cores;
|
||||
|
||||
for (x = minX; x < maxX; x++) {
|
||||
sum_r = sum_g = sum_b =
|
||||
sum_in_r = sum_in_g = sum_in_b =
|
||||
sum_out_r = sum_out_g = sum_out_b = 0;
|
||||
|
||||
src_i = x; // x,0
|
||||
for (i = 0; i <= radius; i++) {
|
||||
stack_i = i;
|
||||
stack[stack_i] = src[src_i];
|
||||
sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
|
||||
sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
|
||||
sum_b += (src[src_i] & 0xff) * (i + 1);
|
||||
sum_out_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_out_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_out_b += (src[src_i] & 0xff);
|
||||
}
|
||||
for (i = 1; i <= radius; i++) {
|
||||
if (i <= hm) src_i += w; // +stride
|
||||
|
||||
stack_i = i + radius;
|
||||
stack[stack_i] = src[src_i];
|
||||
sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
|
||||
sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
|
||||
sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
|
||||
sum_in_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_in_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_in_b += (src[src_i] & 0xff);
|
||||
}
|
||||
|
||||
sp = radius;
|
||||
yp = radius;
|
||||
if (yp > hm) yp = hm;
|
||||
src_i = x + yp * w; // img.pix_ptr(x, yp);
|
||||
dst_i = x; // img.pix_ptr(x, 0);
|
||||
for (y = 0; y < h; y++) {
|
||||
src[dst_i] = (int)
|
||||
((src[dst_i] & 0xff000000) |
|
||||
((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
|
||||
((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
|
||||
((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
|
||||
dst_i += w;
|
||||
|
||||
sum_r -= sum_out_r;
|
||||
sum_g -= sum_out_g;
|
||||
sum_b -= sum_out_b;
|
||||
|
||||
stack_start = sp + div - radius;
|
||||
if (stack_start >= div) stack_start -= div;
|
||||
stack_i = stack_start;
|
||||
|
||||
sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_out_b -= (stack[stack_i] & 0xff);
|
||||
|
||||
if (yp < hm) {
|
||||
src_i += w; // stride
|
||||
++yp;
|
||||
}
|
||||
|
||||
stack[stack_i] = src[src_i];
|
||||
|
||||
sum_in_r += ((src[src_i] >>> 16) & 0xff);
|
||||
sum_in_g += ((src[src_i] >>> 8) & 0xff);
|
||||
sum_in_b += (src[src_i] & 0xff);
|
||||
sum_r += sum_in_r;
|
||||
sum_g += sum_in_g;
|
||||
sum_b += sum_in_b;
|
||||
|
||||
++sp;
|
||||
if (sp >= div) sp = 0;
|
||||
stack_i = sp;
|
||||
|
||||
sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_out_b += (stack[stack_i] & 0xff);
|
||||
sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
|
||||
sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
|
||||
sum_in_b -= (stack[stack_i] & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlurTask implements Callable<Void> {
|
||||
private final int[] _src;
|
||||
private final int _w;
|
||||
private final int _h;
|
||||
private final int _radius;
|
||||
private final int _totalCores;
|
||||
private final int _coreIndex;
|
||||
private final int _round;
|
||||
|
||||
public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) {
|
||||
_src = src;
|
||||
_w = w;
|
||||
_h = h;
|
||||
_radius = radius;
|
||||
_totalCores = totalCores;
|
||||
_coreIndex = coreIndex;
|
||||
_round = round;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class StopWatch {
|
||||
private long startTime;
|
||||
private long previousElapsedTime;
|
||||
private boolean isRunning;
|
||||
|
||||
public void start() {
|
||||
synchronized (this) {
|
||||
startTime = System.currentTimeMillis();
|
||||
isRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
synchronized (this) {
|
||||
previousElapsedTime += System.currentTimeMillis() - startTime;
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
synchronized (this) {
|
||||
startTime = 0;
|
||||
previousElapsedTime = 0;
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public final long getElapsedTime() {
|
||||
synchronized (this) {
|
||||
long currentElapsedTime = 0;
|
||||
if (isRunning) {
|
||||
currentElapsedTime = System.currentTimeMillis() - startTime;
|
||||
}
|
||||
|
||||
return previousElapsedTime + currentElapsedTime;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.getDefault(), "%d millis", getElapsedTime());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.dkanada.gramophone.helper;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
public class SwipeAndDragHelper extends ItemTouchHelper.Callback {
|
||||
|
||||
private ActionCompletionContract contract;
|
||||
|
||||
public SwipeAndDragHelper(ActionCompletionContract contract) {
|
||||
this.contract = contract;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
|
||||
return makeMovementFlags(dragFlags, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
||||
contract.onViewMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||
float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth());
|
||||
viewHolder.itemView.setAlpha(alpha);
|
||||
}
|
||||
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
public interface ActionCompletionContract {
|
||||
void onViewMoved(int oldPosition, int newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.dkanada.gramophone.helper.menu;
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
|
||||
import com.dkanada.gramophone.dialogs.DeletePlaylistDialog;
|
||||
import com.dkanada.gramophone.dialogs.RenamePlaylistDialog;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PlaylistMenuHelper {
|
||||
public static boolean handleMenuClick(@NonNull AppCompatActivity activity, @NonNull final Playlist playlist, @NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_play:
|
||||
MusicPlayerRemote.openQueue(new ArrayList<>(getPlaylistSongs(activity, playlist)), 0, true);
|
||||
return true;
|
||||
case R.id.action_play_next:
|
||||
MusicPlayerRemote.playNext(new ArrayList<>(getPlaylistSongs(activity, playlist)));
|
||||
return true;
|
||||
case R.id.action_add_to_queue:
|
||||
MusicPlayerRemote.enqueue(new ArrayList<>(getPlaylistSongs(activity, playlist)));
|
||||
return true;
|
||||
case R.id.action_add_to_playlist:
|
||||
AddToPlaylistDialog.create(new ArrayList<>(getPlaylistSongs(activity, playlist))).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return true;
|
||||
case R.id.action_rename_playlist:
|
||||
RenamePlaylistDialog.create(playlist).show(activity.getSupportFragmentManager(), "RENAME_PLAYLIST");
|
||||
return true;
|
||||
case R.id.action_delete_playlist:
|
||||
DeletePlaylistDialog.create(playlist).show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static List<Song> getPlaylistSongs(@NonNull Activity activity, Playlist playlist) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.dkanada.gramophone.helper.menu;
|
||||
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
|
||||
import com.dkanada.gramophone.dialogs.SongDetailDialog;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
|
||||
public class SongMenuHelper {
|
||||
public static final int MENU_RES = R.menu.menu_item_song;
|
||||
|
||||
public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull Song song, int menuItemId) {
|
||||
switch (menuItemId) {
|
||||
case R.id.action_share:
|
||||
activity.startActivity(Intent.createChooser(MusicUtil.createShareSongFileIntent(song, activity), null));
|
||||
return true;
|
||||
case R.id.action_add_to_playlist:
|
||||
AddToPlaylistDialog.create(song).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return true;
|
||||
case R.id.action_play_next:
|
||||
MusicPlayerRemote.playNext(song);
|
||||
return true;
|
||||
case R.id.action_add_to_queue:
|
||||
MusicPlayerRemote.enqueue(song);
|
||||
return true;
|
||||
case R.id.action_details:
|
||||
SongDetailDialog.create(song).show(activity.getSupportFragmentManager(), "SONG_DETAILS");
|
||||
return true;
|
||||
case R.id.action_go_to_album:
|
||||
NavigationUtil.goToAlbum(activity, new Album(song));
|
||||
return true;
|
||||
case R.id.action_go_to_artist:
|
||||
NavigationUtil.goToArtist(activity, new Artist(song));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static abstract class OnClickSongMenu implements View.OnClickListener, PopupMenu.OnMenuItemClickListener {
|
||||
private AppCompatActivity activity;
|
||||
|
||||
public OnClickSongMenu(@NonNull AppCompatActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public int getMenuRes() {
|
||||
return MENU_RES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
PopupMenu popupMenu = new PopupMenu(activity, v);
|
||||
popupMenu.inflate(getMenuRes());
|
||||
popupMenu.setOnMenuItemClickListener(this);
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
return handleMenuClick(activity, getSong(), item.getItemId());
|
||||
}
|
||||
|
||||
public abstract Song getSong();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.dkanada.gramophone.helper.menu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SongsMenuHelper {
|
||||
public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull List<Song> songs, int menuItemId) {
|
||||
switch (menuItemId) {
|
||||
case R.id.action_play_next:
|
||||
MusicPlayerRemote.playNext(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_queue:
|
||||
MusicPlayerRemote.enqueue(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_playlist:
|
||||
AddToPlaylistDialog.create(songs).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.dkanada.gramophone.helper.sort;
|
||||
|
||||
public class SortMethod {
|
||||
public static final String NAME = "NAME";
|
||||
public static final String ALBUM = "ALBUM";
|
||||
public static final String ARTIST = "ARTIST";
|
||||
public static final String YEAR = "YEAR";
|
||||
public static final String RANDOM = "RANDOM";
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.dkanada.gramophone.helper.sort;
|
||||
|
||||
public class SortOrder {
|
||||
public static final String ASCENDING = "ASCENDING";
|
||||
public static final String DESCENDING = "DESCENDING";
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.dkanada.gramophone.interfaces;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
|
||||
public interface CabHolder {
|
||||
|
||||
@NonNull
|
||||
MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.dkanada.gramophone.interfaces;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MediaCallback {
|
||||
void onLoadMedia(List<?> media);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.dkanada.gramophone.interfaces;
|
||||
|
||||
public interface MusicServiceEventListener {
|
||||
void onServiceConnected();
|
||||
|
||||
void onServiceDisconnected();
|
||||
|
||||
void onQueueChanged();
|
||||
|
||||
void onPlayingMetaChanged();
|
||||
|
||||
void onPlayStateChanged();
|
||||
|
||||
void onRepeatModeChanged();
|
||||
|
||||
void onShuffleModeChanged();
|
||||
|
||||
void onMediaStoreChanged();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.dkanada.gramophone.interfaces;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
public interface PaletteColorHolder {
|
||||
|
||||
@ColorInt
|
||||
int getPaletteColor();
|
||||
}
|
||||
111
app/src/main/java/com/dkanada/gramophone/loader/SongLoader.java
Normal file
111
app/src/main/java/com/dkanada/gramophone/loader/SongLoader.java
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package com.dkanada.gramophone.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.Audio.AudioColumns;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SongLoader {
|
||||
public static final String QUEUE_PRIMARY = "image";
|
||||
public static final String QUEUE_FAVORITE = "favorite";
|
||||
|
||||
protected static final String BASE_SELECTION = AudioColumns.IS_MUSIC + "=1" + " AND " + AudioColumns.TITLE + " != ''";
|
||||
protected static final String[] BASE_PROJECTION = new String[]{
|
||||
BaseColumns._ID,
|
||||
AudioColumns.TITLE,
|
||||
AudioColumns.TRACK,
|
||||
AudioColumns.YEAR,
|
||||
AudioColumns.DURATION,
|
||||
AudioColumns.ALBUM_ID,
|
||||
AudioColumns.ALBUM,
|
||||
AudioColumns.ARTIST_ID,
|
||||
AudioColumns.ARTIST,
|
||||
QUEUE_PRIMARY,
|
||||
QUEUE_FAVORITE,
|
||||
};
|
||||
|
||||
@NonNull
|
||||
public static List<Song> getAllSongs(@NonNull Context context) {
|
||||
Cursor cursor = makeSongCursor(context, null, null);
|
||||
return getSongs(cursor);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Song> getSongs(@Nullable final Cursor cursor) {
|
||||
List<Song> songs = new ArrayList<>();
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
songs.add(getSongFromCursorImpl(cursor));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
if (cursor != null) cursor.close();
|
||||
return songs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Song getSong(@Nullable Cursor cursor) {
|
||||
Song song;
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
song = getSongFromCursorImpl(cursor);
|
||||
} else {
|
||||
song = Song.EMPTY_SONG;
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Song getSongFromCursorImpl(@NonNull Cursor cursor) {
|
||||
final String id = cursor.getString(0);
|
||||
final String title = cursor.getString(1);
|
||||
final int trackNumber = cursor.getInt(2);
|
||||
final int year = cursor.getInt(3);
|
||||
final long duration = cursor.getLong(4);
|
||||
|
||||
final String albumId = cursor.getString(5);
|
||||
final String albumName = cursor.getString(6);
|
||||
|
||||
final String artistId = cursor.getString(7);
|
||||
final String artistName = cursor.getString(8);
|
||||
|
||||
final String primary = cursor.getString(9);
|
||||
final boolean favorite = Boolean.valueOf(cursor.getString(10));
|
||||
|
||||
return new Song(id, title, trackNumber, year, duration, albumId, albumName, artistId, artistName, primary, favorite);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] selectionValues) {
|
||||
return makeSongCursor(context, selection, selectionValues, PreferenceUtil.getInstance(context).getSongSortOrder());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Cursor makeSongCursor(@NonNull final Context context, @Nullable String selection, String[] selectionValues, final String sortOrder) {
|
||||
if (selection != null && !selection.trim().equals("")) {
|
||||
selection = BASE_SELECTION + " AND " + selection;
|
||||
} else {
|
||||
selection = BASE_SELECTION;
|
||||
}
|
||||
|
||||
try {
|
||||
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
BASE_PROJECTION, selection, selectionValues, sortOrder);
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PagerAdapter} that
|
||||
* uses a {@link Fragment} to manage each page. This class also handles
|
||||
* saving and restoring of fragment's state.
|
||||
* <p/>
|
||||
* <p>This version of the pager is more useful when there are a large number
|
||||
* of pages, working more like a list view. When pages are not visible to
|
||||
* the user, their entire fragment may be destroyed, only keeping the saved
|
||||
* state of that fragment. This allows the pager to hold on to much less
|
||||
* memory associated with each visited page as compared to
|
||||
* {@link FragmentPagerAdapter} at the cost of potentially more overhead when
|
||||
* switching between pages.
|
||||
* <p/>
|
||||
* <p>When using FragmentPagerAdapter the host ViewPager must have a
|
||||
* valid ID set.</p>
|
||||
* <p/>
|
||||
* <p>Subclasses only need to implement {@link #getItem(int)}
|
||||
* and {@link #getCount()} to have a working adapter.
|
||||
* <p/>
|
||||
* <p>Here is an example implementation of a pager containing fragments of
|
||||
* lists:
|
||||
* <p/>
|
||||
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java
|
||||
* complete}
|
||||
* <p/>
|
||||
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
|
||||
* <p/>
|
||||
* {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml
|
||||
* complete}
|
||||
* <p/>
|
||||
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
|
||||
* individual fragment's layout is:
|
||||
* <p/>
|
||||
* {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml
|
||||
* complete}
|
||||
*/
|
||||
public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {
|
||||
|
||||
public static final String TAG = CustomFragmentStatePagerAdapter.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final FragmentManager mFragmentManager;
|
||||
private FragmentTransaction mCurTransaction = null;
|
||||
|
||||
private List<Fragment.SavedState> mSavedState = new ArrayList<>();
|
||||
private List<Fragment> mFragments = new ArrayList<>();
|
||||
private Fragment mCurrentPrimaryItem = null;
|
||||
|
||||
public CustomFragmentStatePagerAdapter(FragmentManager fm) {
|
||||
mFragmentManager = fm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Fragment associated with a specified position.
|
||||
*/
|
||||
public abstract Fragment getItem(int position);
|
||||
|
||||
@Override
|
||||
public void startUpdate(ViewGroup container) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
// If we already have this item instantiated, there is nothing
|
||||
// to do. This can happen when we are restoring the entire pager
|
||||
// from its saved state, where the fragment manager has already
|
||||
// taken care of restoring the fragments we previously had instantiated.
|
||||
if (mFragments.size() > position) {
|
||||
Fragment f = mFragments.get(position);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
Fragment fragment = getItem(position);
|
||||
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||
if (mSavedState.size() > position) {
|
||||
Fragment.SavedState fss = mSavedState.get(position);
|
||||
if (fss != null) {
|
||||
fragment.setInitialSavedState(fss);
|
||||
}
|
||||
}
|
||||
while (mFragments.size() <= position) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
fragment.setMenuVisibility(false);
|
||||
fragment.setUserVisibleHint(false);
|
||||
mFragments.set(position, fragment);
|
||||
mCurTransaction.add(container.getId(), fragment);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
|
||||
+ " v=" + ((Fragment) object).getView());
|
||||
while (mSavedState.size() <= position) {
|
||||
mSavedState.add(null);
|
||||
}
|
||||
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
|
||||
mFragments.set(position, null);
|
||||
|
||||
mCurTransaction.remove(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
mCurrentPrimaryItem.setUserVisibleHint(false);
|
||||
}
|
||||
if (fragment != null) {
|
||||
fragment.setMenuVisibility(true);
|
||||
fragment.setUserVisibleHint(true);
|
||||
}
|
||||
mCurrentPrimaryItem = fragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishUpdate(ViewGroup container) {
|
||||
if (mCurTransaction != null) {
|
||||
mCurTransaction.commitAllowingStateLoss();
|
||||
mCurTransaction = null;
|
||||
mFragmentManager.executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(View view, Object object) {
|
||||
return ((Fragment) object).getView() == view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable saveState() {
|
||||
Bundle state = null;
|
||||
if (mSavedState.size() > 0) {
|
||||
state = new Bundle();
|
||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
mSavedState.toArray(fss);
|
||||
state.putParcelableArray("states", fss);
|
||||
}
|
||||
for (int i = 0; i < mFragments.size(); i++) {
|
||||
Fragment f = mFragments.get(i);
|
||||
if (f != null && f.isAdded()) {
|
||||
if (state == null) {
|
||||
state = new Bundle();
|
||||
}
|
||||
String key = "f" + i;
|
||||
mFragmentManager.putFragment(state, key, f);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(Parcelable state, ClassLoader loader) {
|
||||
if (state != null) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
bundle.setClassLoader(loader);
|
||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
mSavedState.clear();
|
||||
mFragments.clear();
|
||||
if (fss != null) {
|
||||
for (Parcelable fs : fss) {
|
||||
mSavedState.add((Fragment.SavedState) fs);
|
||||
}
|
||||
}
|
||||
Iterable<String> keys = bundle.keySet();
|
||||
for (String key : keys) {
|
||||
if (key.startsWith("f")) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
if (f != null) {
|
||||
while (mFragments.size() <= index) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
f.setMenuVisibility(false);
|
||||
mFragments.set(index, f);
|
||||
} else {
|
||||
Log.w(TAG, "Bad fragment at key " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Fragment getFragment(int position) {
|
||||
if (position < mFragments.size() && position >= 0) {
|
||||
return mFragments.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class DialogAsyncTask<Params, Progress, Result> extends WeakContextAsyncTask<Params, Progress, Result> {
|
||||
private final int delay;
|
||||
private WeakReference<Dialog> dialogWeakReference;
|
||||
|
||||
private boolean supposedToBeDismissed;
|
||||
|
||||
public DialogAsyncTask(Context context) {
|
||||
this(context, 0);
|
||||
}
|
||||
|
||||
public DialogAsyncTask(Context context, int showDelay) {
|
||||
super(context);
|
||||
this.delay = showDelay;
|
||||
dialogWeakReference = new WeakReference<>(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
if (delay > 0) {
|
||||
new Handler().postDelayed(this::initAndShowDialog, delay);
|
||||
} else {
|
||||
initAndShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void initAndShowDialog() {
|
||||
Context context = getContext();
|
||||
if (!supposedToBeDismissed && context != null) {
|
||||
Dialog dialog = createDialog(context);
|
||||
dialogWeakReference = new WeakReference<>(dialog);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void onProgressUpdate(Progress... values) {
|
||||
super.onProgressUpdate(values);
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog != null) {
|
||||
onProgressUpdate(dialog, values);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void onProgressUpdate(@NonNull Dialog dialog, Progress... values) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Dialog getDialog() {
|
||||
return dialogWeakReference.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(Result result) {
|
||||
super.onCancelled(result);
|
||||
tryToDismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
super.onPostExecute(result);
|
||||
tryToDismiss();
|
||||
}
|
||||
|
||||
private void tryToDismiss() {
|
||||
supposedToBeDismissed = true;
|
||||
try {
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog != null)
|
||||
dialog.dismiss();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Dialog createDialog(@NonNull Context context);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import android.animation.Animator;
|
||||
|
||||
public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks;
|
||||
import com.github.ksoichiro.android.observablescrollview.ScrollState;
|
||||
|
||||
public abstract class SimpleObservableScrollViewCallbacks implements ObservableScrollViewCallbacks {
|
||||
@Override
|
||||
public void onScrollChanged(int i, boolean b, boolean b2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownMotionEvent() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import android.widget.SeekBar;
|
||||
|
||||
public abstract class SimpleOnSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class WeakContextAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||
private WeakReference<Context> contextWeakReference;
|
||||
|
||||
public WeakContextAsyncTask(Context context) {
|
||||
contextWeakReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Context getContext() {
|
||||
return contextWeakReference.get();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
package com.dkanada.gramophone.misc;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
|
||||
/**
|
||||
* <a href="http://code.google.com/p/android/issues/detail?id=14944">Issue
|
||||
* 14944</a>
|
||||
*
|
||||
* @author Alexander Blom
|
||||
*/
|
||||
public abstract class WrappedAsyncTaskLoader<D> extends AsyncTaskLoader<D> {
|
||||
|
||||
private D mData;
|
||||
|
||||
/**
|
||||
* Constructor of <code>WrappedAsyncTaskLoader</code>
|
||||
*
|
||||
* @param context The {@link Context} to use.
|
||||
*/
|
||||
public WrappedAsyncTaskLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void deliverResult(D data) {
|
||||
if (!isReset()) {
|
||||
this.mData = data;
|
||||
super.deliverResult(data);
|
||||
} else {
|
||||
// An asynchronous query came in while the loader is stopped
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
super.onStartLoading();
|
||||
if (this.mData != null) {
|
||||
deliverResult(this.mData);
|
||||
} else if (takeContentChanged() || this.mData == null) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
super.onStopLoading();
|
||||
// Attempt to cancel the current load task if possible
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
// Ensure the loader is stopped
|
||||
onStopLoading();
|
||||
this.mData = null;
|
||||
}
|
||||
}
|
||||
142
app/src/main/java/com/dkanada/gramophone/model/Album.java
Normal file
142
app/src/main/java/com/dkanada/gramophone/model/Album.java
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.dkanada.gramophone.App;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
import org.jellyfin.apiclient.model.dto.ImageOptions;
|
||||
import org.jellyfin.apiclient.model.entities.ImageType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Album implements Parcelable {
|
||||
public List<Song> songs;
|
||||
|
||||
public String id;
|
||||
public String title;
|
||||
public int year;
|
||||
|
||||
public String artistId;
|
||||
public String artistName;
|
||||
|
||||
public String primary;
|
||||
|
||||
public Album(BaseItemDto itemDto) {
|
||||
this.id = itemDto.getId();
|
||||
this.title = itemDto.getName();
|
||||
this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0;
|
||||
|
||||
if (itemDto.getAlbumArtists().size() != 0) {
|
||||
this.artistId = itemDto.getAlbumArtists().get(0).getId();
|
||||
this.artistName = itemDto.getAlbumArtists().get(0).getName();
|
||||
} else if (itemDto.getArtistItems().size() != 0) {
|
||||
this.artistId = itemDto.getArtistItems().get(0).getId();
|
||||
this.artistName = itemDto.getArtistItems().get(0).getName();
|
||||
}
|
||||
|
||||
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
|
||||
|
||||
this.songs = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Album(Song song) {
|
||||
this.id = song.albumId;
|
||||
this.title = song.albumName;
|
||||
this.year = song.year;
|
||||
|
||||
this.artistId = song.artistId;
|
||||
this.artistName = song.artistName;
|
||||
|
||||
this.primary = song.primary;
|
||||
}
|
||||
|
||||
public Album() {
|
||||
this.songs = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getArtistId() {
|
||||
return artistId;
|
||||
}
|
||||
|
||||
public String getArtistName() {
|
||||
return artistName;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public int getSongCount() {
|
||||
return songs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Album album = (Album) o;
|
||||
return id.equals(album.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(id);
|
||||
dest.writeString(title);
|
||||
dest.writeInt(year);
|
||||
|
||||
dest.writeString(artistId);
|
||||
dest.writeString(artistName);
|
||||
|
||||
dest.writeString(primary);
|
||||
}
|
||||
|
||||
protected Album(Parcel in) {
|
||||
this.songs = new ArrayList<>();
|
||||
|
||||
this.id = in.readString();
|
||||
this.title = in.readString();
|
||||
this.year = in.readInt();
|
||||
|
||||
this.artistId = in.readString();
|
||||
this.artistName = in.readString();
|
||||
|
||||
this.primary = in.readString();
|
||||
}
|
||||
|
||||
public static final Creator<Album> CREATOR = new Creator<Album>() {
|
||||
public Album createFromParcel(Parcel source) {
|
||||
return new Album(source);
|
||||
}
|
||||
|
||||
public Album[] newArray(int size) {
|
||||
return new Album[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
129
app/src/main/java/com/dkanada/gramophone/model/Artist.java
Normal file
129
app/src/main/java/com/dkanada/gramophone/model/Artist.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
import org.jellyfin.apiclient.model.dto.GenreDto;
|
||||
import org.jellyfin.apiclient.model.entities.ImageType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Artist implements Parcelable {
|
||||
public List<Genre> genres;
|
||||
public List<Album> albums;
|
||||
public List<Song> songs;
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
|
||||
public String primary;
|
||||
|
||||
public Artist(BaseItemDto itemDto) {
|
||||
this.id = itemDto.getId();
|
||||
this.name = itemDto.getName();
|
||||
|
||||
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
|
||||
|
||||
this.genres = new ArrayList<>();
|
||||
this.albums = new ArrayList<>();
|
||||
this.songs = new ArrayList<>();
|
||||
|
||||
if (itemDto.getGenreItems() != null) {
|
||||
for (GenreDto genre : itemDto.getGenreItems()) {
|
||||
genres.add(new Genre(genre));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Artist(Album album) {
|
||||
this.id = album.artistId;
|
||||
this.name = album.artistName;
|
||||
}
|
||||
|
||||
public Artist(Song song) {
|
||||
this.id = song.artistId;
|
||||
this.name = song.artistName;
|
||||
}
|
||||
|
||||
public Artist() {
|
||||
this.albums = new ArrayList<>();
|
||||
this.songs = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getSongCount() {
|
||||
return songs.size();
|
||||
}
|
||||
|
||||
public int getAlbumCount() {
|
||||
return albums.size();
|
||||
}
|
||||
|
||||
public List<Song> getSongs() {
|
||||
return songs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Artist artist = (Artist) o;
|
||||
return id.equals(artist.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(id);
|
||||
dest.writeString(name);
|
||||
|
||||
dest.writeString(primary);
|
||||
}
|
||||
|
||||
protected Artist(Parcel in) {
|
||||
this.genres = new ArrayList<>();
|
||||
this.albums = new ArrayList<>();
|
||||
this.songs = new ArrayList<>();
|
||||
|
||||
this.id = in.readString();
|
||||
this.name = in.readString();
|
||||
|
||||
this.primary = in.readString();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Artist> CREATOR = new Parcelable.Creator<Artist>() {
|
||||
@Override
|
||||
public Artist createFromParcel(Parcel source) {
|
||||
return new Artist(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Artist[] newArray(int size) {
|
||||
return new Artist[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
|
||||
public class CategoryInfo implements Parcelable {
|
||||
public Category category;
|
||||
public boolean visible;
|
||||
|
||||
public CategoryInfo(Category category, boolean visible) {
|
||||
this.category = category;
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
private CategoryInfo(Parcel source) {
|
||||
category = (Category) source.readSerializable();
|
||||
visible = source.readInt() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(category);
|
||||
dest.writeInt(visible ? 1 : 0);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<CategoryInfo> CREATOR = new Parcelable.Creator<CategoryInfo>() {
|
||||
public CategoryInfo createFromParcel(Parcel source) {
|
||||
return new CategoryInfo(source);
|
||||
}
|
||||
|
||||
public CategoryInfo[] newArray(int size) {
|
||||
return new CategoryInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
public enum Category {
|
||||
SONGS(R.string.songs),
|
||||
ALBUMS(R.string.albums),
|
||||
ARTISTS(R.string.artists),
|
||||
GENRES(R.string.genres),
|
||||
PLAYLISTS(R.string.playlists);
|
||||
|
||||
public final int stringRes;
|
||||
|
||||
Category(int stringRes) {
|
||||
this.stringRes = stringRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
72
app/src/main/java/com/dkanada/gramophone/model/Genre.java
Normal file
72
app/src/main/java/com/dkanada/gramophone/model/Genre.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
import org.jellyfin.apiclient.model.dto.GenreDto;
|
||||
|
||||
public class Genre implements Parcelable {
|
||||
public final String id;
|
||||
public final String name;
|
||||
public final int songCount;
|
||||
|
||||
public Genre(GenreDto genreDto) {
|
||||
this.id = genreDto.getId();
|
||||
this.name = genreDto.getName();
|
||||
this.songCount = 0;
|
||||
}
|
||||
|
||||
public Genre(BaseItemDto itemDto) {
|
||||
this.id = itemDto.getId();
|
||||
this.name = itemDto.getName();
|
||||
this.songCount = itemDto.getSongCount() != null ? itemDto.getSongCount() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Genre genre = (Genre) o;
|
||||
return id.equals(genre.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.id);
|
||||
dest.writeString(this.name);
|
||||
dest.writeInt(this.songCount);
|
||||
}
|
||||
|
||||
protected Genre(Parcel in) {
|
||||
this.id = in.readString();
|
||||
this.name = in.readString();
|
||||
this.songCount = in.readInt();
|
||||
}
|
||||
|
||||
public static final Creator<Genre> CREATOR = new Creator<Genre>() {
|
||||
public Genre createFromParcel(Parcel source) {
|
||||
return new Genre(source);
|
||||
}
|
||||
|
||||
public Genre[] newArray(int size) {
|
||||
return new Genre[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
71
app/src/main/java/com/dkanada/gramophone/model/Playlist.java
Normal file
71
app/src/main/java/com/dkanada/gramophone/model/Playlist.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
|
||||
public class Playlist implements Parcelable {
|
||||
public final String id;
|
||||
public final String name;
|
||||
|
||||
public Playlist(BaseItemDto itemDto) {
|
||||
this.id = itemDto.getId();
|
||||
this.name = itemDto.getName();
|
||||
}
|
||||
|
||||
public Playlist(final int id, final String name) {
|
||||
this.id = Integer.toString(id);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Playlist() {
|
||||
this.id = "";
|
||||
this.name = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Playlist playlist = (Playlist) o;
|
||||
return id.equals(playlist.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.id);
|
||||
dest.writeString(this.name);
|
||||
}
|
||||
|
||||
protected Playlist(Parcel in) {
|
||||
this.id = in.readString();
|
||||
this.name = in.readString();
|
||||
}
|
||||
|
||||
public static final Creator<Playlist> CREATOR = new Creator<Playlist>() {
|
||||
public Playlist createFromParcel(Parcel source) {
|
||||
return new Playlist(source);
|
||||
}
|
||||
|
||||
public Playlist[] newArray(int size) {
|
||||
return new Playlist[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
|
||||
public class PlaylistSong extends Song {
|
||||
public final String playlistId;
|
||||
public final String indexId;
|
||||
|
||||
public PlaylistSong(BaseItemDto itemDto, String playlist) {
|
||||
super(itemDto);
|
||||
|
||||
this.playlistId = playlist;
|
||||
this.indexId = itemDto.getPlaylistItemId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
|
||||
dest.writeString(this.playlistId);
|
||||
dest.writeString(this.indexId);
|
||||
}
|
||||
|
||||
protected PlaylistSong(Parcel in) {
|
||||
super(in);
|
||||
|
||||
this.playlistId = in.readString();
|
||||
this.indexId = in.readString();
|
||||
}
|
||||
|
||||
public static final Creator<PlaylistSong> CREATOR = new Creator<PlaylistSong>() {
|
||||
public PlaylistSong createFromParcel(Parcel source) {
|
||||
return new PlaylistSong(source);
|
||||
}
|
||||
|
||||
public PlaylistSong[] newArray(int size) {
|
||||
return new PlaylistSong[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
133
app/src/main/java/com/dkanada/gramophone/model/Song.java
Normal file
133
app/src/main/java/com/dkanada/gramophone/model/Song.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package com.dkanada.gramophone.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
|
||||
public class Song implements Parcelable {
|
||||
public static final Song EMPTY_SONG = new Song(null, "", -1, -1, -1, null, "", null, "", null, false);
|
||||
|
||||
public final String id;
|
||||
public final String title;
|
||||
public final int trackNumber;
|
||||
public final int year;
|
||||
public final long duration;
|
||||
|
||||
public final String albumId;
|
||||
public final String albumName;
|
||||
|
||||
public String artistId;
|
||||
public String artistName;
|
||||
|
||||
public String primary;
|
||||
public boolean favorite;
|
||||
|
||||
public Song(BaseItemDto itemDto) {
|
||||
this.id = itemDto.getId();
|
||||
this.title = itemDto.getName();
|
||||
this.trackNumber = itemDto.getIndexNumber() != null ? itemDto.getIndexNumber() : 0;
|
||||
this.year = itemDto.getProductionYear() != null ? itemDto.getProductionYear() : 0;
|
||||
this.duration = itemDto.getRunTimeTicks() != null ? itemDto.getRunTimeTicks() / 10000 : 0;
|
||||
|
||||
this.albumId = itemDto.getAlbumId();
|
||||
this.albumName = itemDto.getAlbum();
|
||||
|
||||
if (itemDto.getAlbumArtists().size() != 0) {
|
||||
this.artistId = itemDto.getAlbumArtists().get(0).getId();
|
||||
this.artistName = itemDto.getAlbumArtists().get(0).getName();
|
||||
} else if (itemDto.getArtistItems().size() != 0) {
|
||||
this.artistId = itemDto.getArtistItems().get(0).getId();
|
||||
this.artistName = itemDto.getArtistItems().get(0).getName();
|
||||
}
|
||||
|
||||
this.primary = itemDto.getAlbumPrimaryImageTag() != null ? albumId : null;
|
||||
this.favorite = itemDto.getUserData() != null && itemDto.getUserData().getIsFavorite();
|
||||
}
|
||||
|
||||
public Song(String id, String title, int trackNumber, int year, long duration, String albumId, String albumName, String artistId, String artistName, String primary, boolean favorite) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.trackNumber = trackNumber;
|
||||
this.year = year;
|
||||
this.duration = duration;
|
||||
|
||||
this.albumId = albumId;
|
||||
this.albumName = albumName;
|
||||
|
||||
this.artistId = artistId;
|
||||
this.artistName = artistName;
|
||||
|
||||
this.primary = primary;
|
||||
this.favorite = favorite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Song song = (Song) o;
|
||||
return id.equals(song.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.id);
|
||||
dest.writeString(this.title);
|
||||
dest.writeInt(this.trackNumber);
|
||||
dest.writeInt(this.year);
|
||||
dest.writeLong(this.duration);
|
||||
|
||||
dest.writeString(this.albumId);
|
||||
dest.writeString(this.albumName);
|
||||
|
||||
dest.writeString(this.artistId);
|
||||
dest.writeString(this.artistName);
|
||||
|
||||
dest.writeString(this.primary);
|
||||
dest.writeString(Boolean.toString(favorite));
|
||||
}
|
||||
|
||||
protected Song(Parcel in) {
|
||||
this.id = in.readString();
|
||||
this.title = in.readString();
|
||||
this.trackNumber = in.readInt();
|
||||
this.year = in.readInt();
|
||||
this.duration = in.readLong();
|
||||
|
||||
this.albumId = in.readString();
|
||||
this.albumName = in.readString();
|
||||
|
||||
this.artistId = in.readString();
|
||||
this.artistName = in.readString();
|
||||
|
||||
this.primary = in.readString();
|
||||
this.favorite = Boolean.valueOf(in.readString());
|
||||
}
|
||||
|
||||
public static final Creator<Song> CREATOR = new Creator<Song>() {
|
||||
public Song createFromParcel(Parcel source) {
|
||||
return new Song(source);
|
||||
}
|
||||
|
||||
public Song[] newArray(int size) {
|
||||
return new Song[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.dkanada.gramophone.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEDialogPreference;
|
||||
|
||||
public class LibraryPreference extends ATEDialogPreference {
|
||||
public LibraryPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public LibraryPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LibraryPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LibraryPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.dkanada.gramophone.preferences;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.CategoryInfoAdapter;
|
||||
import com.dkanada.gramophone.model.CategoryInfo;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LibraryPreferenceDialog extends DialogFragment {
|
||||
public static LibraryPreferenceDialog newInstance() {
|
||||
return new LibraryPreferenceDialog();
|
||||
}
|
||||
|
||||
private CategoryInfoAdapter adapter;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
View view = getActivity().getLayoutInflater().inflate(R.layout.preference_dialog_library_categories, null);
|
||||
|
||||
List<CategoryInfo> categoryInfos;
|
||||
if (savedInstanceState != null) {
|
||||
categoryInfos = savedInstanceState.getParcelableArrayList(PreferenceUtil.LIBRARY_CATEGORIES);
|
||||
} else {
|
||||
categoryInfos = PreferenceUtil.getInstance(getContext()).getLibraryCategories();
|
||||
}
|
||||
|
||||
adapter = new CategoryInfoAdapter(categoryInfos);
|
||||
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
adapter.attachToRecyclerView(recyclerView);
|
||||
|
||||
return new MaterialDialog.Builder(getContext())
|
||||
.title(R.string.library_categories)
|
||||
.customView(view, false)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.neutralText(R.string.reset_action)
|
||||
.autoDismiss(false)
|
||||
.onNeutral((dialog, action) -> adapter.setCategoryInfos(PreferenceUtil.getInstance(getContext()).getDefaultLibraryCategories()))
|
||||
.onNegative((dialog, action) -> dismiss())
|
||||
.onPositive((dialog, action) -> {
|
||||
updateCategories(adapter.getCategoryInfos());
|
||||
dismiss();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList(PreferenceUtil.LIBRARY_CATEGORIES, new ArrayList<>(adapter.getCategoryInfos()));
|
||||
}
|
||||
|
||||
private void updateCategories(List<CategoryInfo> categories) {
|
||||
if (getSelected(categories) == 0) return;
|
||||
|
||||
PreferenceUtil.getInstance(getContext()).setLibraryCategories(categories);
|
||||
}
|
||||
|
||||
private int getSelected(List<CategoryInfo> categories) {
|
||||
int selected = 0;
|
||||
for (CategoryInfo categoryInfo : categories) {
|
||||
if (categoryInfo.visible)
|
||||
selected++;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.dkanada.gramophone.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEDialogPreference;
|
||||
|
||||
public class NowPlayingScreenPreference extends ATEDialogPreference {
|
||||
public NowPlayingScreenPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NowPlayingScreenPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NowPlayingScreenPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public NowPlayingScreenPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.dkanada.gramophone.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.heinrichreimersoftware.materialintro.view.InkPageIndicator;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.ui.fragments.player.NowPlayingScreen;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
|
||||
public class NowPlayingScreenPreferenceDialog extends DialogFragment implements MaterialDialog.SingleButtonCallback, ViewPager.OnPageChangeListener {
|
||||
|
||||
private DialogAction whichButtonClicked;
|
||||
private int viewPagerPosition;
|
||||
|
||||
public static NowPlayingScreenPreferenceDialog newInstance() {
|
||||
return new NowPlayingScreenPreferenceDialog();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
@SuppressLint("InflateParams") View view = LayoutInflater.from(getContext()).inflate(R.layout.preference_dialog_now_playing_screen, null);
|
||||
ViewPager viewPager = view.findViewById(R.id.now_playing_screen_view_pager);
|
||||
viewPager.setAdapter(new NowPlayingScreenAdapter(getContext()));
|
||||
viewPager.addOnPageChangeListener(this);
|
||||
viewPager.setPageMargin((int) ViewUtil.convertDpToPixel(32, getResources()));
|
||||
viewPager.setCurrentItem(PreferenceUtil.getInstance(getContext()).getNowPlayingScreen().ordinal());
|
||||
|
||||
InkPageIndicator pageIndicator = view.findViewById(R.id.page_indicator);
|
||||
pageIndicator.setViewPager(viewPager);
|
||||
pageIndicator.onPageSelected(viewPager.getCurrentItem());
|
||||
|
||||
return new MaterialDialog.Builder(getContext())
|
||||
.title(R.string.pref_title_now_playing_screen_appearance)
|
||||
.positiveText(android.R.string.ok)
|
||||
.negativeText(android.R.string.cancel)
|
||||
.onAny(this)
|
||||
.customView(view, false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
whichButtonClicked = which;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (whichButtonClicked == DialogAction.POSITIVE) {
|
||||
PreferenceUtil.getInstance(getContext()).setNowPlayingScreen(NowPlayingScreen.values()[viewPagerPosition]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
this.viewPagerPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
|
||||
private static class NowPlayingScreenAdapter extends PagerAdapter {
|
||||
private Context context;
|
||||
|
||||
public NowPlayingScreenAdapter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
|
||||
NowPlayingScreen nowPlayingScreen = NowPlayingScreen.values()[position];
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.preference_now_playing_screen_item, collection, false);
|
||||
collection.addView(layout);
|
||||
|
||||
ImageView image = layout.findViewById(R.id.image);
|
||||
TextView title = layout.findViewById(R.id.title);
|
||||
image.setImageResource(nowPlayingScreen.drawableResId);
|
||||
title.setText(nowPlayingScreen.titleRes);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
|
||||
collection.removeView((View) view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return NowPlayingScreen.values().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return context.getString(NowPlayingScreen.values()[position].titleRes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The CyanogenMod Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.dkanada.gramophone.provider;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore.Audio.AudioColumns;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.dkanada.gramophone.loader.SongLoader;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QueueStore extends SQLiteOpenHelper {
|
||||
@Nullable
|
||||
private static QueueStore sInstance = null;
|
||||
public static final String DATABASE_NAME = "music_playback_state.db";
|
||||
public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue";
|
||||
public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue";
|
||||
private static final int VERSION = 5;
|
||||
|
||||
public QueueStore(final Context context) {
|
||||
super(context, DATABASE_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@NonNull final SQLiteDatabase db) {
|
||||
createTable(db, PLAYING_QUEUE_TABLE_NAME);
|
||||
createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
private void createTable(@NonNull final SQLiteDatabase db, final String tableName) {
|
||||
// noinspection StringBufferReplaceableByString
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("CREATE TABLE IF NOT EXISTS ");
|
||||
builder.append(tableName);
|
||||
builder.append("(");
|
||||
|
||||
builder.append(BaseColumns._ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.TITLE);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.TRACK);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.YEAR);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.DURATION);
|
||||
builder.append(" LONG NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ALBUM_ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ALBUM);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ARTIST_ID);
|
||||
builder.append(" INT NOT NULL,");
|
||||
|
||||
builder.append(AudioColumns.ARTIST);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(SongLoader.QUEUE_PRIMARY);
|
||||
builder.append(" STRING NOT NULL,");
|
||||
|
||||
builder.append(SongLoader.QUEUE_FAVORITE);
|
||||
builder.append(" STRING NOT NULL);");
|
||||
|
||||
db.execSQL(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized QueueStore getInstance(@NonNull final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new QueueStore(context.getApplicationContext());
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public synchronized void saveQueues(@NonNull final List<Song> playingQueue, @NonNull final List<Song> originalPlayingQueue) {
|
||||
saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue);
|
||||
saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue);
|
||||
}
|
||||
|
||||
private synchronized void saveQueue(final String tableName, @NonNull final List<Song> queue) {
|
||||
final SQLiteDatabase database = getWritableDatabase();
|
||||
database.beginTransaction();
|
||||
|
||||
try {
|
||||
database.delete(tableName, null, null);
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
final int NUM_PROCESS = 20;
|
||||
int position = 0;
|
||||
while (position < queue.size()) {
|
||||
database.beginTransaction();
|
||||
try {
|
||||
for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) {
|
||||
Song song = queue.get(i);
|
||||
ContentValues values = new ContentValues(4);
|
||||
|
||||
values.put(BaseColumns._ID, song.id);
|
||||
values.put(AudioColumns.TITLE, song.title);
|
||||
values.put(AudioColumns.TRACK, song.trackNumber);
|
||||
values.put(AudioColumns.YEAR, song.year);
|
||||
values.put(AudioColumns.DURATION, song.duration);
|
||||
values.put(AudioColumns.ALBUM_ID, song.albumId);
|
||||
values.put(AudioColumns.ALBUM, song.albumName);
|
||||
values.put(AudioColumns.ARTIST_ID, song.artistId);
|
||||
values.put(AudioColumns.ARTIST, song.artistName);
|
||||
values.put(SongLoader.QUEUE_PRIMARY, song.primary);
|
||||
values.put(SongLoader.QUEUE_FAVORITE, song.favorite);
|
||||
|
||||
database.insert(tableName, null, values);
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
position += NUM_PROCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Song> getSavedPlayingQueue() {
|
||||
return getQueue(PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Song> getSavedOriginalPlayingQueue() {
|
||||
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Song> getQueue(@NonNull final String tableName) {
|
||||
Cursor cursor = getReadableDatabase().query(tableName, null,
|
||||
null, null, null, null, null);
|
||||
return SongLoader.getSongs(cursor);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project Licensed under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
|
||||
* or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// Modified for Phonograph by Karim Abou Zeid (kabouzeid).
|
||||
|
||||
package com.dkanada.gramophone.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.dkanada.gramophone.BuildConfig;
|
||||
|
||||
/**
|
||||
* Used to control headset playback.
|
||||
* Single press: pause/resume
|
||||
* Double press: next track
|
||||
* Triple press: previous track
|
||||
*/
|
||||
public class MediaButtonIntentReceiver extends BroadcastReceiver {
|
||||
private static final boolean DEBUG = BuildConfig.DEBUG;
|
||||
public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
|
||||
|
||||
private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;
|
||||
|
||||
private static final int DOUBLE_CLICK = 400;
|
||||
|
||||
private static WakeLock mWakeLock = null;
|
||||
private static int mClickCounter = 0;
|
||||
private static long mLastClickTime = 0;
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
private static Handler mHandler = new Handler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(final Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:
|
||||
final int clickCount = msg.arg1;
|
||||
final String command;
|
||||
|
||||
if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount);
|
||||
switch (clickCount) {
|
||||
case 1:
|
||||
command = MusicService.ACTION_TOGGLE_PAUSE;
|
||||
break;
|
||||
case 2:
|
||||
command = MusicService.ACTION_SKIP;
|
||||
break;
|
||||
case 3:
|
||||
command = MusicService.ACTION_REWIND;
|
||||
break;
|
||||
default:
|
||||
command = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command != null) {
|
||||
final Context context = (Context) msg.obj;
|
||||
startService(context, command);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
releaseWakeLockIfHandlerIdle();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
if (DEBUG) Log.v(TAG, "Received intent: " + intent);
|
||||
if (handleIntent(context, intent) && isOrderedBroadcast()) {
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handleIntent(final Context context, final Intent intent) {
|
||||
final String intentAction = intent.getAction();
|
||||
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
|
||||
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||
if (event == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int keycode = event.getKeyCode();
|
||||
final int action = event.getAction();
|
||||
|
||||
// fallback to system time if event time is not available
|
||||
final long eventTime = event.getEventTime() != 0
|
||||
? event.getEventTime()
|
||||
: System.currentTimeMillis();
|
||||
|
||||
String command = null;
|
||||
switch (keycode) {
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
command = MusicService.ACTION_STOP;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
command = MusicService.ACTION_TOGGLE_PAUSE;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
command = MusicService.ACTION_SKIP;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
command = MusicService.ACTION_REWIND;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
command = MusicService.ACTION_PAUSE;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
command = MusicService.ACTION_PLAY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command != null) {
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
if (event.getRepeatCount() == 0) {
|
||||
// Only consider the first event in a sequence, not the repeat events,
|
||||
// so that we don't trigger in cases where the first event went to
|
||||
// a different app (e.g. when the user ends a phone call by
|
||||
// long pressing the headset button)
|
||||
|
||||
// The service may or may not be running, but we need to send it
|
||||
// a command.
|
||||
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
|
||||
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
|
||||
mClickCounter = 0;
|
||||
}
|
||||
|
||||
mClickCounter++;
|
||||
if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter);
|
||||
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);
|
||||
|
||||
Message msg = mHandler.obtainMessage(
|
||||
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context);
|
||||
|
||||
long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0;
|
||||
if (mClickCounter >= 3) {
|
||||
mClickCounter = 0;
|
||||
}
|
||||
|
||||
mLastClickTime = eventTime;
|
||||
acquireWakeLockAndSendMessage(context, msg, delay);
|
||||
} else {
|
||||
startService(context, command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void startService(Context context, String command) {
|
||||
final Intent intent = new Intent(context, MusicService.class);
|
||||
intent.setAction(command);
|
||||
try {
|
||||
// IMPORTANT NOTE: (kind of a hack)
|
||||
// on Android O and above the following crashes when the app is not running
|
||||
// there is no good way to check whether the app is running so we catch the exception
|
||||
// we do not always want to use startForegroundService() because then one gets an ANR
|
||||
// if no notification is displayed via startForeground()
|
||||
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
|
||||
context.startService(intent);
|
||||
} catch (IllegalStateException ignored) {
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {
|
||||
if (mWakeLock == null) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Phonograph headset button");
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what);
|
||||
|
||||
mWakeLock.acquire(10000);
|
||||
mHandler.sendMessageDelayed(msg, delay);
|
||||
}
|
||||
|
||||
private static void releaseWakeLockIfHandlerIdle() {
|
||||
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
|
||||
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mWakeLock != null) {
|
||||
if (DEBUG) Log.v(TAG, "Releasing wake lock");
|
||||
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
package com.dkanada.gramophone.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.service.playback.Playback;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
|
||||
public static final String TAG = MultiPlayer.class.getSimpleName();
|
||||
|
||||
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
|
||||
private MediaPlayer mNextMediaPlayer;
|
||||
|
||||
private Context context;
|
||||
|
||||
@Nullable
|
||||
private Playback.PlaybackCallbacks callbacks;
|
||||
|
||||
private boolean mIsInitialized = false;
|
||||
|
||||
public MultiPlayer(final Context context) {
|
||||
this.context = context;
|
||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
* @return True if the <code>player</code> has been prepared and is
|
||||
* ready to play, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean setDataSource(@NonNull final String path) {
|
||||
mIsInitialized = false;
|
||||
mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
|
||||
if (mIsInitialized) {
|
||||
setNextDataSource(null);
|
||||
}
|
||||
|
||||
return mIsInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The {@link MediaPlayer} to use
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
* @return True if the <code>player</code> has been prepared and is
|
||||
* ready to play, false otherwise
|
||||
*/
|
||||
private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
player.reset();
|
||||
player.setOnPreparedListener(null);
|
||||
if (path.startsWith("content://")) {
|
||||
player.setDataSource(context, Uri.parse(path));
|
||||
} else {
|
||||
player.setDataSource(path);
|
||||
}
|
||||
|
||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
player.prepare();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
player.setOnCompletionListener(this);
|
||||
player.setOnErrorListener(this);
|
||||
|
||||
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
|
||||
|
||||
context.sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MediaPlayer to start when this MediaPlayer finishes playback.
|
||||
*
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
*/
|
||||
@Override
|
||||
public void setNextDataSource(@Nullable final String path) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.i(TAG, "Next media player is current one, continuing");
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Media player not initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PreferenceUtil.getInstance(context).getGaplessPlayback()) {
|
||||
mNextMediaPlayer = new MediaPlayer();
|
||||
mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
|
||||
if (setDataSourceImpl(mNextMediaPlayer, path)) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
|
||||
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e);
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallbacks(@Nullable Playback.PlaybackCallbacks callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return mIsInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
try {
|
||||
mCurrentMediaPlayer.start();
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mCurrentMediaPlayer.reset();
|
||||
mIsInitialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
stop();
|
||||
|
||||
mCurrentMediaPlayer.release();
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pause() {
|
||||
try {
|
||||
mCurrentMediaPlayer.pause();
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int duration() {
|
||||
if (!mIsInitialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return mCurrentMediaPlayer.getDuration();
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int position() {
|
||||
if (!mIsInitialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return mCurrentMediaPlayer.getCurrentPosition();
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int seek(final int whereto) {
|
||||
try {
|
||||
mCurrentMediaPlayer.seekTo(whereto);
|
||||
return whereto;
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVolume(final float vol) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setVolume(vol, vol);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAudioSessionId(final int sessionId) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setAudioSessionId(sessionId);
|
||||
return true;
|
||||
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioSessionId() {
|
||||
return mCurrentMediaPlayer.getAudioSessionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
|
||||
mIsInitialized = false;
|
||||
mCurrentMediaPlayer.release();
|
||||
mCurrentMediaPlayer = new MediaPlayer();
|
||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
if (context != null) {
|
||||
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompletion(final MediaPlayer mp) {
|
||||
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
||||
mIsInitialized = false;
|
||||
mCurrentMediaPlayer.release();
|
||||
mCurrentMediaPlayer = mNextMediaPlayer;
|
||||
mIsInitialized = true;
|
||||
mNextMediaPlayer = null;
|
||||
if (callbacks != null) callbacks.onTrackWentToNext();
|
||||
} else {
|
||||
if (callbacks != null) callbacks.onTrackEnded();
|
||||
}
|
||||
}
|
||||
}
|
||||
1342
app/src/main/java/com/dkanada/gramophone/service/MusicService.java
Normal file
1342
app/src/main/java/com/dkanada/gramophone/service/MusicService.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,77 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public abstract class PlayingNotification {
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
static final String NOTIFICATION_CHANNEL_ID = "playing_notification";
|
||||
|
||||
private static final int NOTIFY_MODE_FOREGROUND = 1;
|
||||
private static final int NOTIFY_MODE_BACKGROUND = 0;
|
||||
|
||||
private int notifyMode = NOTIFY_MODE_BACKGROUND;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
protected MusicService service;
|
||||
boolean stopped;
|
||||
|
||||
public synchronized void init(MusicService service) {
|
||||
this.service = service;
|
||||
notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void update();
|
||||
|
||||
public synchronized void stop() {
|
||||
stopped = true;
|
||||
service.stopForeground(true);
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
void updateNotifyModeAndPostNotification(Notification notification) {
|
||||
int newNotifyMode;
|
||||
if (service.isPlaying()) {
|
||||
newNotifyMode = NOTIFY_MODE_FOREGROUND;
|
||||
} else {
|
||||
newNotifyMode = NOTIFY_MODE_BACKGROUND;
|
||||
}
|
||||
|
||||
if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||
service.stopForeground(false);
|
||||
}
|
||||
|
||||
if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
|
||||
service.startForeground(NOTIFICATION_ID, notification);
|
||||
} else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
notifyMode = newNotifyMode;
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
private void createNotificationChannel() {
|
||||
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||
if (notificationChannel == null) {
|
||||
notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, service.getString(R.string.playing_notification_name), NotificationManager.IMPORTANCE_LOW);
|
||||
notificationChannel.setDescription(service.getString(R.string.playing_notification_description));
|
||||
notificationChannel.enableLights(false);
|
||||
notificationChannel.enableVibration(false);
|
||||
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.ui.activities.MainActivity;
|
||||
import com.dkanada.gramophone.util.ImageUtil;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
public class PlayingNotificationImpl extends PlayingNotification {
|
||||
|
||||
private Target<BitmapPaletteWrapper> target;
|
||||
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
stopped = false;
|
||||
|
||||
final Song song = service.getCurrentSong();
|
||||
|
||||
final boolean isPlaying = service.isPlaying();
|
||||
|
||||
final RemoteViews notificationLayout = new RemoteViews(service.getPackageName(), R.layout.notification);
|
||||
final RemoteViews notificationLayoutBig = new RemoteViews(service.getPackageName(), R.layout.notification_big);
|
||||
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
notificationLayout.setViewVisibility(R.id.media_titles, View.INVISIBLE);
|
||||
} else {
|
||||
notificationLayout.setViewVisibility(R.id.media_titles, View.VISIBLE);
|
||||
notificationLayout.setTextViewText(R.id.title, song.title);
|
||||
notificationLayout.setTextViewText(R.id.text, song.artistName);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName) && TextUtils.isEmpty(song.albumName)) {
|
||||
notificationLayoutBig.setViewVisibility(R.id.media_titles, View.INVISIBLE);
|
||||
} else {
|
||||
notificationLayoutBig.setViewVisibility(R.id.media_titles, View.VISIBLE);
|
||||
notificationLayoutBig.setTextViewText(R.id.title, song.title);
|
||||
notificationLayoutBig.setTextViewText(R.id.text, song.artistName);
|
||||
notificationLayoutBig.setTextViewText(R.id.text2, song.albumName);
|
||||
}
|
||||
|
||||
linkButtons(notificationLayout, notificationLayoutBig);
|
||||
|
||||
Intent action = new Intent(service, MainActivity.class);
|
||||
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0);
|
||||
final PendingIntent deleteIntent = buildPendingIntent(service, MusicService.ACTION_QUIT, null);
|
||||
|
||||
final Notification notification = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(clickIntent)
|
||||
.setDeleteIntent(deleteIntent)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContent(notificationLayout)
|
||||
.setCustomBigContentView(notificationLayoutBig)
|
||||
.setOngoing(isPlaying)
|
||||
.build();
|
||||
|
||||
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
|
||||
service.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (target != null) {
|
||||
Glide.clear(target);
|
||||
}
|
||||
target = CustomGlideRequest.Builder.from(Glide.with(service), song.primary)
|
||||
.generatePalette(service).build()
|
||||
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
|
||||
@Override
|
||||
public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation<? super BitmapPaletteWrapper> glideAnimation) {
|
||||
update(resource.getBitmap(), ThemeUtil.getColor(resource.getPalette(), Color.TRANSPARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
super.onLoadFailed(e, errorDrawable);
|
||||
update(null, Color.WHITE);
|
||||
}
|
||||
|
||||
private void update(@Nullable Bitmap bitmap, int bgColor) {
|
||||
if (bitmap != null) {
|
||||
notificationLayout.setImageViewBitmap(R.id.image, bitmap);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.image, bitmap);
|
||||
} else {
|
||||
notificationLayout.setImageViewResource(R.id.image, R.drawable.default_album_art);
|
||||
notificationLayoutBig.setImageViewResource(R.id.image, R.drawable.default_album_art);
|
||||
}
|
||||
|
||||
if (!PreferenceUtil.getInstance(service).getColoredNotification()) {
|
||||
bgColor = Color.WHITE;
|
||||
}
|
||||
setBackgroundColor(bgColor);
|
||||
setNotificationContent(ColorUtil.isColorLight(bgColor));
|
||||
|
||||
// notification has been stopped before loading was finished
|
||||
if (stopped) return;
|
||||
|
||||
updateNotifyModeAndPostNotification(notification);
|
||||
}
|
||||
|
||||
private void setBackgroundColor(int color) {
|
||||
notificationLayout.setInt(R.id.root, "setBackgroundColor", color);
|
||||
notificationLayoutBig.setInt(R.id.root, "setBackgroundColor", color);
|
||||
}
|
||||
|
||||
private void setNotificationContent(boolean dark) {
|
||||
int primary = MaterialValueHelper.getPrimaryTextColor(service, dark);
|
||||
int secondary = MaterialValueHelper.getSecondaryTextColor(service, dark);
|
||||
|
||||
Bitmap prev = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary), 1.5f);
|
||||
Bitmap next = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, primary), 1.5f);
|
||||
Bitmap playPause = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp, primary), 1.5f);
|
||||
|
||||
notificationLayout.setTextColor(R.id.title, primary);
|
||||
notificationLayout.setTextColor(R.id.text, secondary);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_prev, prev);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_next, next);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_play_pause, playPause);
|
||||
|
||||
notificationLayoutBig.setTextColor(R.id.title, primary);
|
||||
notificationLayoutBig.setTextColor(R.id.text, secondary);
|
||||
notificationLayoutBig.setTextColor(R.id.text2, secondary);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_prev, prev);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_next, next);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_play_pause, playPause);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void linkButtons(final RemoteViews notificationLayout, final RemoteViews notificationLayoutBig) {
|
||||
PendingIntent pendingIntent;
|
||||
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_REWIND, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, pendingIntent);
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_TOGGLE_PAUSE, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent);
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_SKIP, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_next, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_next, pendingIntent);
|
||||
}
|
||||
|
||||
private PendingIntent buildPendingIntent(Context context, final String action, final ComponentName serviceName) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.setComponent(serviceName);
|
||||
return PendingIntent.getService(context, 0, intent, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media.app.NotificationCompat.MediaStyle;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.ui.activities.MainActivity;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_REWIND;
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_SKIP;
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_TOGGLE_PAUSE;
|
||||
|
||||
public class PlayingNotificationImpl24 extends PlayingNotification {
|
||||
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
stopped = false;
|
||||
|
||||
final Song song = service.getCurrentSong();
|
||||
|
||||
final boolean isPlaying = service.isPlaying();
|
||||
|
||||
final int playButtonResId = isPlaying
|
||||
? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
|
||||
|
||||
Intent action = new Intent(service, MainActivity.class);
|
||||
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0);
|
||||
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
Intent intent = new Intent(MusicService.ACTION_QUIT);
|
||||
intent.setComponent(serviceName);
|
||||
final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0);
|
||||
|
||||
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
|
||||
service.runOnUiThread(() -> CustomGlideRequest.Builder
|
||||
.from(Glide.with(service), song.primary)
|
||||
.generatePalette(service).build()
|
||||
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
|
||||
@Override
|
||||
public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation<? super BitmapPaletteWrapper> glideAnimation) {
|
||||
Palette palette = resource.getPalette();
|
||||
update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
update(null, Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
void update(Bitmap bitmap, int color) {
|
||||
if (bitmap == null)
|
||||
bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art);
|
||||
NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId,
|
||||
service.getString(R.string.action_play_pause),
|
||||
retrievePlaybackAction(ACTION_TOGGLE_PAUSE));
|
||||
NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp,
|
||||
service.getString(R.string.action_previous),
|
||||
retrievePlaybackAction(ACTION_REWIND));
|
||||
NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp,
|
||||
service.getString(R.string.action_next),
|
||||
retrievePlaybackAction(ACTION_SKIP));
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setSubText(song.albumName)
|
||||
.setLargeIcon(bitmap)
|
||||
.setContentIntent(clickIntent)
|
||||
.setDeleteIntent(deleteIntent)
|
||||
.setContentTitle(song.title)
|
||||
.setContentText(song.artistName)
|
||||
.setOngoing(isPlaying)
|
||||
.setShowWhen(false)
|
||||
.addAction(previousAction)
|
||||
.addAction(playPauseAction)
|
||||
.addAction(nextAction);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.setStyle(new MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2))
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).getColoredNotification())
|
||||
builder.setColor(color);
|
||||
}
|
||||
|
||||
// notification has been stopped before loading was finished
|
||||
if (stopped) return;
|
||||
|
||||
updateNotifyModeAndPostNotification(builder.build());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private PendingIntent retrievePlaybackAction(final String action) {
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
Intent intent = new Intent(action);
|
||||
intent.setComponent(serviceName);
|
||||
return PendingIntent.getService(service, 0, intent, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.dkanada.gramophone.service.playback;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface Playback {
|
||||
boolean setDataSource(String path);
|
||||
|
||||
void setNextDataSource(@Nullable String path);
|
||||
|
||||
void setCallbacks(PlaybackCallbacks callbacks);
|
||||
|
||||
boolean isInitialized();
|
||||
|
||||
boolean start();
|
||||
|
||||
void stop();
|
||||
|
||||
void release();
|
||||
|
||||
boolean pause();
|
||||
|
||||
boolean isPlaying();
|
||||
|
||||
int duration();
|
||||
|
||||
int position();
|
||||
|
||||
int seek(int whereto);
|
||||
|
||||
boolean setVolume(float vol);
|
||||
|
||||
boolean setAudioSessionId(int sessionId);
|
||||
|
||||
int getAudioSessionId();
|
||||
|
||||
interface PlaybackCallbacks {
|
||||
void onTrackWentToNext();
|
||||
|
||||
void onTrackEnded();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.dkanada.gramophone.shortcuts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.util.ImageUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
public final class AppShortcutIconGenerator {
|
||||
|
||||
public static Icon generateThemedIcon(Context context, int iconId) {
|
||||
if (PreferenceUtil.getInstance(context).getColoredShortcuts()) {
|
||||
return generateUserThemedIcon(context, iconId).toIcon();
|
||||
} else {
|
||||
return generateDefaultThemedIcon(context, iconId).toIcon();
|
||||
}
|
||||
}
|
||||
|
||||
private static IconCompat generateDefaultThemedIcon(Context context, int iconId) {
|
||||
// Return an Icon of iconId with default colors
|
||||
return generateThemedIcon(context, iconId,
|
||||
context.getColor(R.color.app_shortcut_default_foreground),
|
||||
context.getColor(R.color.app_shortcut_default_background)
|
||||
);
|
||||
}
|
||||
|
||||
private static IconCompat generateUserThemedIcon(Context context, int iconId) {
|
||||
// Get background color from context's theme
|
||||
final TypedValue typedColorBackground = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true);
|
||||
|
||||
// Return an Icon of iconId with those colors
|
||||
return generateThemedIcon(context, iconId,
|
||||
ThemeStore.primaryColor(context),
|
||||
typedColorBackground.data
|
||||
);
|
||||
}
|
||||
|
||||
private static IconCompat generateThemedIcon(Context context, int iconId, int foregroundColor, int backgroundColor) {
|
||||
// Get and tint foreground and background drawables
|
||||
Drawable vectorDrawable = ImageUtil.getTintedVectorDrawable(context, iconId, foregroundColor);
|
||||
Drawable backgroundDrawable = ImageUtil.getTintedVectorDrawable(context, R.drawable.ic_app_shortcut_background, backgroundColor);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(backgroundDrawable, vectorDrawable);
|
||||
return IconCompat.createWithAdaptiveBitmap(ImageUtil.createBitmap(adaptiveIconDrawable));
|
||||
} else {
|
||||
// Squash the two drawables together
|
||||
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, vectorDrawable});
|
||||
|
||||
// Return as an Icon
|
||||
return IconCompat.createWithBitmap(ImageUtil.createBitmap(layerDrawable));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.dkanada.gramophone.shortcuts;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.LatestShortcutType;
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.ShuffleShortcutType;
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.FrequentShortcutType;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
|
||||
public class AppShortcutLauncherActivity extends Activity {
|
||||
public static final String KEY_SHORTCUT_TYPE = "com.kabouzeid.gramophone.shortcuts.ShortcutType";
|
||||
|
||||
public static final int SHORTCUT_TYPE_SHUFFLE = 0;
|
||||
public static final int SHORTCUT_TYPE_FREQUENT = 1;
|
||||
public static final int SHORTCUT_TYPE_LATEST = 2;
|
||||
public static final int SHORTCUT_TYPE_NONE = 3;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
int shortcutType = SHORTCUT_TYPE_NONE;
|
||||
|
||||
// Set shortcutType from the intent extras
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
//noinspection WrongConstant
|
||||
shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE);
|
||||
}
|
||||
|
||||
switch (shortcutType) {
|
||||
case SHORTCUT_TYPE_SHUFFLE:
|
||||
DynamicShortcutManager.reportShortcutUsed(this, ShuffleShortcutType.getId());
|
||||
break;
|
||||
case SHORTCUT_TYPE_FREQUENT:
|
||||
DynamicShortcutManager.reportShortcutUsed(this, FrequentShortcutType.getId());
|
||||
break;
|
||||
case SHORTCUT_TYPE_LATEST:
|
||||
DynamicShortcutManager.reportShortcutUsed(this, LatestShortcutType.getId());
|
||||
break;
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private void startServiceWithPlaylist(int shuffleMode, Playlist playlist) {
|
||||
Intent intent = new Intent(this, MusicService.class);
|
||||
intent.setAction(MusicService.ACTION_PLAY_PLAYLIST);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(MusicService.INTENT_EXTRA_PLAYLIST, playlist);
|
||||
bundle.putInt(MusicService.INTENT_EXTRA_SHUFFLE_MODE, shuffleMode);
|
||||
|
||||
intent.putExtras(bundle);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.dkanada.gramophone.shortcuts;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.LatestShortcutType;
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.ShuffleShortcutType;
|
||||
import com.dkanada.gramophone.shortcuts.shortcuttype.FrequentShortcutType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
public class DynamicShortcutManager {
|
||||
|
||||
private Context context;
|
||||
private ShortcutManager shortcutManager;
|
||||
|
||||
public DynamicShortcutManager(Context context) {
|
||||
this.context = context;
|
||||
shortcutManager = this.context.getSystemService(ShortcutManager.class);
|
||||
}
|
||||
|
||||
public static ShortcutInfo createShortcut(Context context, String id, String shortLabel, String longLabel, Icon icon, Intent intent) {
|
||||
return new ShortcutInfo.Builder(context, id)
|
||||
.setShortLabel(shortLabel)
|
||||
.setLongLabel(longLabel)
|
||||
.setIcon(icon)
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void initDynamicShortcuts() {
|
||||
if (shortcutManager.getDynamicShortcuts().size() == 0) {
|
||||
shortcutManager.setDynamicShortcuts(getDefaultShortcuts());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDynamicShortcuts() {
|
||||
shortcutManager.updateShortcuts(getDefaultShortcuts());
|
||||
}
|
||||
|
||||
public List<ShortcutInfo> getDefaultShortcuts() {
|
||||
return (Arrays.asList(
|
||||
new ShuffleShortcutType(context).getShortcutInfo(),
|
||||
new FrequentShortcutType(context).getShortcutInfo(),
|
||||
new LatestShortcutType(context).getShortcutInfo()
|
||||
));
|
||||
}
|
||||
|
||||
public static void reportShortcutUsed(Context context, String shortcutId){
|
||||
context.getSystemService(ShortcutManager.class).reportShortcutUsed(shortcutId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.dkanada.gramophone.shortcuts.shortcuttype;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutLauncherActivity;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
public abstract class BaseShortcutType {
|
||||
|
||||
static final String ID_PREFIX = "com.kabouzeid.gramophone.shortcuts.id.";
|
||||
|
||||
Context context;
|
||||
|
||||
public BaseShortcutType(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
static public String getId() {
|
||||
return ID_PREFIX + "invalid";
|
||||
}
|
||||
|
||||
abstract ShortcutInfo getShortcutInfo();
|
||||
|
||||
/**
|
||||
* Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode
|
||||
*
|
||||
* @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
|
||||
* @return
|
||||
*/
|
||||
Intent getPlaySongsIntent(int shortcutType) {
|
||||
Intent intent = new Intent(context, AppShortcutLauncherActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType);
|
||||
|
||||
intent.putExtras(b);
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.dkanada.gramophone.shortcuts.shortcuttype;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutIconGenerator;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutLauncherActivity;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
public final class FrequentShortcutType extends BaseShortcutType {
|
||||
public FrequentShortcutType(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static String getId() {
|
||||
return ID_PREFIX + "top_tracks";
|
||||
}
|
||||
|
||||
public ShortcutInfo getShortcutInfo() {
|
||||
return new ShortcutInfo.Builder(context, getId())
|
||||
.setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short))
|
||||
.setLongLabel(context.getString(R.string.my_top_tracks))
|
||||
.setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_top_tracks))
|
||||
.setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_FREQUENT))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.dkanada.gramophone.shortcuts.shortcuttype;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutIconGenerator;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutLauncherActivity;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
public final class LatestShortcutType extends BaseShortcutType {
|
||||
public LatestShortcutType(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static String getId() {
|
||||
return ID_PREFIX + "last_added";
|
||||
}
|
||||
|
||||
public ShortcutInfo getShortcutInfo() {
|
||||
return new ShortcutInfo.Builder(context, getId())
|
||||
.setShortLabel(context.getString(R.string.app_shortcut_last_added_short))
|
||||
.setLongLabel(context.getString(R.string.last_added))
|
||||
.setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_last_added))
|
||||
.setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LATEST))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.dkanada.gramophone.shortcuts.shortcuttype;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutIconGenerator;
|
||||
import com.dkanada.gramophone.shortcuts.AppShortcutLauncherActivity;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
public final class ShuffleShortcutType extends BaseShortcutType {
|
||||
public ShuffleShortcutType(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static String getId() {
|
||||
return ID_PREFIX + "shuffle_all";
|
||||
}
|
||||
|
||||
public ShortcutInfo getShortcutInfo() {
|
||||
return new ShortcutInfo.Builder(context, getId())
|
||||
.setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short))
|
||||
.setLongLabel(context.getString(R.string.action_shuffle_all))
|
||||
.setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all))
|
||||
.setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.AppCompatButton;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsBaseActivity;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class AboutActivity extends AbsBaseActivity implements View.OnClickListener {
|
||||
|
||||
private static String GITHUB = "https://github.com/dkanada/gelli";
|
||||
|
||||
private static String TWITTER = "https://twitter.com/karimjabouzeid";
|
||||
private static String WEBSITE = "https://github.com/dkanada";
|
||||
|
||||
private static String TRANSLATE = "https://phonograph.oneskyapp.com/collaboration/project?id=26521";
|
||||
private static String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=com.kabouzeid.gramophone";
|
||||
|
||||
private static String AIDAN_FOLLESTAD_GOOGLE_PLUS = "https://google.com/+AidanFollestad";
|
||||
private static String AIDAN_FOLLESTAD_GITHUB = "https://github.com/afollestad";
|
||||
|
||||
private static String MAARTEN_CORPEL_GOOGLE_PLUS = "https://google.com/+MaartenCorpel";
|
||||
|
||||
private static String ALEKSANDAR_TESIC_GOOGLE_PLUS = "https://google.com/+aleksandartešić";
|
||||
|
||||
private static String EUGENE_CHEUNG_GITHUB = "https://github.com/arkon";
|
||||
private static String EUGENE_CHEUNG_WEBSITE = "https://echeung.me/";
|
||||
|
||||
private static String ADRIAN_TWITTER = "https://twitter.com/froschgames";
|
||||
private static String ADRIAN_WEBSITE = "https://froschgames.com/";
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(R.id.app_version)
|
||||
TextView appVersion;
|
||||
@BindView(R.id.app_source)
|
||||
LinearLayout appSource;
|
||||
@BindView(R.id.write_an_email)
|
||||
LinearLayout writeAnEmail;
|
||||
@BindView(R.id.follow_on_twitter)
|
||||
LinearLayout followOnTwitter;
|
||||
@BindView(R.id.visit_website)
|
||||
LinearLayout visitWebsite;
|
||||
@BindView(R.id.report_bugs)
|
||||
LinearLayout reportBugs;
|
||||
@BindView(R.id.translate)
|
||||
LinearLayout translate;
|
||||
@BindView(R.id.donate)
|
||||
LinearLayout donate;
|
||||
@BindView(R.id.rate_on_google_play)
|
||||
LinearLayout rateOnGooglePlay;
|
||||
@BindView(R.id.aidan_follestad_google_plus)
|
||||
AppCompatButton aidanFollestadGooglePlus;
|
||||
@BindView(R.id.aidan_follestad_git_hub)
|
||||
AppCompatButton aidanFollestadGitHub;
|
||||
@BindView(R.id.maarten_corpel_google_plus)
|
||||
AppCompatButton maartenCorpelGooglePlus;
|
||||
@BindView(R.id.aleksandar_tesic_google_plus)
|
||||
AppCompatButton aleksandarTesicGooglePlus;
|
||||
@BindView(R.id.eugene_cheung_git_hub)
|
||||
AppCompatButton eugeneCheungGitHub;
|
||||
@BindView(R.id.eugene_cheung_website)
|
||||
AppCompatButton eugeneCheungWebsite;
|
||||
@BindView(R.id.adrian_twitter)
|
||||
AppCompatButton adrianTwitter;
|
||||
@BindView(R.id.adrian_website)
|
||||
AppCompatButton adrianWebsite;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
setUpViews();
|
||||
}
|
||||
|
||||
private void setUpViews() {
|
||||
setUpToolbar();
|
||||
setUpAppVersion();
|
||||
setUpOnClickListeners();
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
private void setUpAppVersion() {
|
||||
appVersion.setText(getCurrentVersionName(this));
|
||||
}
|
||||
|
||||
private void setUpOnClickListeners() {
|
||||
followOnTwitter.setOnClickListener(this);
|
||||
appSource.setOnClickListener(this);
|
||||
visitWebsite.setOnClickListener(this);
|
||||
reportBugs.setOnClickListener(this);
|
||||
writeAnEmail.setOnClickListener(this);
|
||||
translate.setOnClickListener(this);
|
||||
rateOnGooglePlay.setOnClickListener(this);
|
||||
donate.setOnClickListener(this);
|
||||
aidanFollestadGooglePlus.setOnClickListener(this);
|
||||
aidanFollestadGitHub.setOnClickListener(this);
|
||||
maartenCorpelGooglePlus.setOnClickListener(this);
|
||||
aleksandarTesicGooglePlus.setOnClickListener(this);
|
||||
eugeneCheungGitHub.setOnClickListener(this);
|
||||
eugeneCheungWebsite.setOnClickListener(this);
|
||||
adrianTwitter.setOnClickListener(this);
|
||||
adrianWebsite.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private static String getCurrentVersionName(@NonNull final Context context) {
|
||||
try {
|
||||
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == followOnTwitter) {
|
||||
openUrl(TWITTER);
|
||||
} else if (v == appSource) {
|
||||
openUrl(GITHUB);
|
||||
} else if (v == visitWebsite) {
|
||||
openUrl(WEBSITE);
|
||||
} else if (v == reportBugs) {
|
||||
openUrl(GITHUB);
|
||||
} else if (v == writeAnEmail) {
|
||||
Intent intent = new Intent(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse("mailto:contact@kabouzeid.com"));
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, "contact@kabouzeid.com");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, "Phonograph");
|
||||
startActivity(Intent.createChooser(intent, "E-Mail"));
|
||||
} else if (v == translate) {
|
||||
openUrl(TRANSLATE);
|
||||
} else if (v == rateOnGooglePlay) {
|
||||
openUrl(RATE_ON_GOOGLE_PLAY);
|
||||
} else if (v == donate) {
|
||||
openUrl(RATE_ON_GOOGLE_PLAY);
|
||||
} else if (v == aidanFollestadGooglePlus) {
|
||||
openUrl(AIDAN_FOLLESTAD_GOOGLE_PLUS);
|
||||
} else if (v == aidanFollestadGitHub) {
|
||||
openUrl(AIDAN_FOLLESTAD_GITHUB);
|
||||
} else if (v == maartenCorpelGooglePlus) {
|
||||
openUrl(MAARTEN_CORPEL_GOOGLE_PLUS);
|
||||
} else if (v == aleksandarTesicGooglePlus) {
|
||||
openUrl(ALEKSANDAR_TESIC_GOOGLE_PLUS);
|
||||
} else if (v == eugeneCheungGitHub) {
|
||||
openUrl(EUGENE_CHEUNG_GITHUB);
|
||||
} else if (v == eugeneCheungWebsite) {
|
||||
openUrl(EUGENE_CHEUNG_WEBSITE);
|
||||
} else if (v == adrianTwitter) {
|
||||
openUrl(ADRIAN_TWITTER);
|
||||
} else if (v == adrianWebsite) {
|
||||
openUrl(ADRIAN_WEBSITE);
|
||||
}
|
||||
}
|
||||
|
||||
private void openUrl(String url) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.afollestad.materialdialogs.util.DialogUtils;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.song.AlbumSongAdapter;
|
||||
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
|
||||
import com.dkanada.gramophone.dialogs.SleepTimerDialog;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
|
||||
import com.dkanada.gramophone.misc.SimpleObservableScrollViewCallbacks;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.NavigationUtil;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
|
||||
import org.jellyfin.apiclient.model.querying.ItemQuery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AlbumDetailActivity extends AbsSlidingMusicPanelActivity implements PaletteColorHolder, CabHolder {
|
||||
public static final String EXTRA_ALBUM = "extra_album";
|
||||
|
||||
private Album album;
|
||||
|
||||
@BindView(R.id.list)
|
||||
ObservableRecyclerView recyclerView;
|
||||
@BindView(R.id.image)
|
||||
ImageView albumArtImageView;
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(R.id.header)
|
||||
View headerView;
|
||||
@BindView(R.id.header_overlay)
|
||||
View headerOverlay;
|
||||
|
||||
@BindView(R.id.artist_icon)
|
||||
ImageView artistIconImageView;
|
||||
@BindView(R.id.duration_icon)
|
||||
ImageView durationIconImageView;
|
||||
@BindView(R.id.song_count_icon)
|
||||
ImageView songCountIconImageView;
|
||||
@BindView(R.id.album_year_icon)
|
||||
ImageView albumYearIconImageView;
|
||||
@BindView(R.id.artist_text)
|
||||
TextView artistTextView;
|
||||
@BindView(R.id.duration_text)
|
||||
TextView durationTextView;
|
||||
@BindView(R.id.song_count_text)
|
||||
TextView songCountTextView;
|
||||
@BindView(R.id.album_year_text)
|
||||
TextView albumYearTextView;
|
||||
|
||||
private AlbumSongAdapter adapter;
|
||||
|
||||
private MaterialCab cab;
|
||||
private int headerViewHeight;
|
||||
private int toolbarColor;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setUpObservableListViewParams();
|
||||
setUpToolBar();
|
||||
setUpViews();
|
||||
|
||||
if (Build.VERSION.SDK_INT > 21) postponeEnterTransition();
|
||||
Album album = getIntent().getExtras().getParcelable(EXTRA_ALBUM);
|
||||
loadAlbumCover(album.primary);
|
||||
setAlbum(album);
|
||||
|
||||
ItemQuery query = new ItemQuery();
|
||||
query.setParentId(album.id);
|
||||
query.setSortBy(new String[]{"IndexNumber"});
|
||||
|
||||
QueryUtil.getSongs(query, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
album.songs = (List<Song>) media;
|
||||
setAlbum(album);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createContentView() {
|
||||
return wrapSlidingMusicPanel(R.layout.activity_album_detail);
|
||||
}
|
||||
|
||||
private final SimpleObservableScrollViewCallbacks observableScrollViewCallbacks = new SimpleObservableScrollViewCallbacks() {
|
||||
@Override
|
||||
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
|
||||
scrollY += headerViewHeight;
|
||||
|
||||
// Change alpha of overlay
|
||||
float headerAlpha = Math.max(0, Math.min(1, (float) 2 * scrollY / headerViewHeight));
|
||||
headerOverlay.setBackgroundColor(ColorUtil.withAlpha(toolbarColor, headerAlpha));
|
||||
|
||||
// Translate name text
|
||||
headerView.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
headerOverlay.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
albumArtImageView.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
}
|
||||
};
|
||||
|
||||
private void setUpObservableListViewParams() {
|
||||
headerViewHeight = getResources().getDimensionPixelSize(R.dimen.detail_header_height);
|
||||
}
|
||||
|
||||
private void setUpViews() {
|
||||
setUpRecyclerView();
|
||||
setUpSongsAdapter();
|
||||
artistTextView.setOnClickListener(v -> {
|
||||
if (album != null) {
|
||||
NavigationUtil.goToArtist(AlbumDetailActivity.this, new Artist(album));
|
||||
}
|
||||
});
|
||||
|
||||
setColors(DialogUtils.resolveColor(this, R.attr.defaultFooterColor));
|
||||
}
|
||||
|
||||
private void loadAlbumCover(String primary) {
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(this), primary)
|
||||
.generatePalette(this).build()
|
||||
.listener(new RequestListener<Object, BitmapPaletteWrapper>() {
|
||||
@Override
|
||||
public boolean onException(Exception e, Object model, Target<BitmapPaletteWrapper> target, boolean isFirstResource) {
|
||||
if (Build.VERSION.SDK_INT > 21) startPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(BitmapPaletteWrapper resource, Object model, Target<BitmapPaletteWrapper> target, boolean dataSource, boolean isFirstResource) {
|
||||
if (Build.VERSION.SDK_INT > 21) startPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.dontAnimate()
|
||||
.into(new CustomPaletteTarget(albumArtImageView) {
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
setColors(color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setColors(int color) {
|
||||
toolbarColor = color;
|
||||
headerView.setBackgroundColor(color);
|
||||
|
||||
setNavigationbarColor(color);
|
||||
setTaskDescriptionColor(color);
|
||||
|
||||
toolbar.setBackgroundColor(color);
|
||||
// needed to auto readjust the toolbar content color
|
||||
setSupportActionBar(toolbar);
|
||||
setStatusbarColor(color);
|
||||
|
||||
int secondaryTextColor = MaterialValueHelper.getSecondaryTextColor(this, ColorUtil.isColorLight(color));
|
||||
artistIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
durationIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
songCountIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
albumYearIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
artistTextView.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color)));
|
||||
durationTextView.setTextColor(secondaryTextColor);
|
||||
songCountTextView.setTextColor(secondaryTextColor);
|
||||
albumYearTextView.setTextColor(secondaryTextColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPaletteColor() {
|
||||
return toolbarColor;
|
||||
}
|
||||
|
||||
private void setUpRecyclerView() {
|
||||
setUpRecyclerViewPadding();
|
||||
recyclerView.setScrollViewCallbacks(observableScrollViewCallbacks);
|
||||
final View contentView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||
contentView.post(() -> observableScrollViewCallbacks.onScrollChanged(-headerViewHeight, false, false));
|
||||
}
|
||||
|
||||
private void setUpRecyclerViewPadding() {
|
||||
recyclerView.setPadding(0, headerViewHeight, 0, 0);
|
||||
}
|
||||
|
||||
private void setUpToolBar() {
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setTitle(null);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
private void setUpSongsAdapter() {
|
||||
adapter = new AlbumSongAdapter(this, getAlbum().songs, R.layout.item_list, false, this);
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(this, 1));
|
||||
recyclerView.setAdapter(adapter);
|
||||
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
if (adapter.getItemCount() == 0) finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_album_detail, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final List<Song> songs = adapter.getDataSet();
|
||||
switch (id) {
|
||||
case R.id.action_sleep_timer:
|
||||
new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
|
||||
return true;
|
||||
case R.id.action_shuffle_album:
|
||||
MusicPlayerRemote.openAndShuffleQueue(songs, true);
|
||||
return true;
|
||||
case R.id.action_play_next:
|
||||
MusicPlayerRemote.playNext(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_queue:
|
||||
MusicPlayerRemote.enqueue(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_playlist:
|
||||
AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
super.onBackPressed();
|
||||
return true;
|
||||
case R.id.action_go_to_artist:
|
||||
NavigationUtil.goToArtist(this, new Artist(album));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MaterialCab openCab(int menuRes, @NonNull final MaterialCab.Callback callback) {
|
||||
if (cab != null && cab.isActive()) cab.finish();
|
||||
cab = new MaterialCab(this, R.id.cab_stub)
|
||||
.setMenu(menuRes)
|
||||
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
|
||||
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(getPaletteColor()))
|
||||
.start(new MaterialCab.Callback() {
|
||||
@Override
|
||||
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
|
||||
return callback.onCabCreated(materialCab, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabItemClicked(MenuItem menuItem) {
|
||||
return callback.onCabItemClicked(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabFinished(MaterialCab materialCab) {
|
||||
return callback.onCabFinished(materialCab);
|
||||
}
|
||||
});
|
||||
return cab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (cab != null && cab.isActive()) {
|
||||
cab.finish();
|
||||
} else {
|
||||
recyclerView.stopScroll();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
super.onMediaStoreChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusbarColor(int color) {
|
||||
super.setStatusbarColor(color);
|
||||
setLightStatusbar(false);
|
||||
}
|
||||
|
||||
private void setAlbum(Album album) {
|
||||
this.album = album;
|
||||
|
||||
getSupportActionBar().setTitle(album.getTitle());
|
||||
artistTextView.setText(album.getArtistName());
|
||||
songCountTextView.setText(MusicUtil.getSongCountString(this, album.getSongCount()));
|
||||
durationTextView.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, album.songs)));
|
||||
albumYearTextView.setText(MusicUtil.getYearString(album.getYear()));
|
||||
|
||||
if (album.songs.size() != 0) adapter.swapDataSet(album.songs);
|
||||
}
|
||||
|
||||
private Album getAlbum() {
|
||||
if (album == null) album = new Album();
|
||||
return album;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.afollestad.materialdialogs.util.DialogUtils;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.github.ksoichiro.android.observablescrollview.ObservableListView;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.album.HorizontalAlbumAdapter;
|
||||
import com.dkanada.gramophone.adapter.song.ArtistSongAdapter;
|
||||
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
|
||||
import com.dkanada.gramophone.dialogs.SleepTimerDialog;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.CustomPaletteTarget;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
|
||||
import com.dkanada.gramophone.misc.SimpleObservableScrollViewCallbacks;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
|
||||
import org.jellyfin.apiclient.model.querying.ItemQuery;
|
||||
|
||||
public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implements PaletteColorHolder, CabHolder {
|
||||
public static final String EXTRA_ARTIST = "extra_artist";
|
||||
|
||||
@BindView(R.id.list)
|
||||
ObservableListView songListView;
|
||||
@BindView(R.id.image)
|
||||
ImageView artistImage;
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(R.id.header)
|
||||
View headerView;
|
||||
@BindView(R.id.header_overlay)
|
||||
View headerOverlay;
|
||||
|
||||
@BindView(R.id.duration_icon)
|
||||
ImageView durationIconImageView;
|
||||
@BindView(R.id.song_count_icon)
|
||||
ImageView songCountIconImageView;
|
||||
@BindView(R.id.album_count_icon)
|
||||
ImageView albumCountIconImageView;
|
||||
@BindView(R.id.duration_text)
|
||||
TextView durationTextView;
|
||||
@BindView(R.id.song_count_text)
|
||||
TextView songCountTextView;
|
||||
@BindView(R.id.album_count_text)
|
||||
TextView albumCountTextView;
|
||||
|
||||
View songListHeader;
|
||||
RecyclerView albumRecyclerView;
|
||||
|
||||
private MaterialCab cab;
|
||||
private int headerViewHeight;
|
||||
private int toolbarColor;
|
||||
|
||||
private Artist artist;
|
||||
private HorizontalAlbumAdapter albumAdapter;
|
||||
private ArtistSongAdapter songAdapter;
|
||||
|
||||
private final SimpleObservableScrollViewCallbacks observableScrollViewCallbacks = new SimpleObservableScrollViewCallbacks() {
|
||||
@Override
|
||||
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
|
||||
scrollY += headerViewHeight;
|
||||
|
||||
// Change alpha of overlay
|
||||
float headerAlpha = Math.max(0, Math.min(1, (float) 2 * scrollY / headerViewHeight));
|
||||
headerOverlay.setBackgroundColor(ColorUtil.withAlpha(toolbarColor, headerAlpha));
|
||||
|
||||
// Translate name text
|
||||
headerView.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
headerOverlay.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
artistImage.setTranslationY(Math.max(-scrollY, -headerViewHeight));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
usePalette = PreferenceUtil.getInstance(this).getAlbumArtistColoredFooters();
|
||||
|
||||
initViews();
|
||||
setUpObservableListViewParams();
|
||||
setUpToolbar();
|
||||
setUpViews();
|
||||
|
||||
if (Build.VERSION.SDK_INT > 21) postponeEnterTransition();
|
||||
Artist artist = getIntent().getExtras().getParcelable(EXTRA_ARTIST);
|
||||
loadArtistImage(artist.primary);
|
||||
setArtist(artist);
|
||||
|
||||
ItemQuery albums = new ItemQuery();
|
||||
albums.setArtistIds(new String[]{artist.id});
|
||||
QueryUtil.getAlbums(albums, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
artist.albums = (List<Album>) media;
|
||||
setArtist(artist);
|
||||
}
|
||||
});
|
||||
|
||||
ItemQuery songs = new ItemQuery();
|
||||
songs.setArtistIds(new String[]{artist.id});
|
||||
QueryUtil.getSongs(songs, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
artist.songs = (List<Song>) media;
|
||||
setArtist(artist);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createContentView() {
|
||||
return wrapSlidingMusicPanel(R.layout.activity_artist_detail);
|
||||
}
|
||||
|
||||
private boolean usePalette;
|
||||
|
||||
private void setUpObservableListViewParams() {
|
||||
headerViewHeight = getResources().getDimensionPixelSize(R.dimen.detail_header_height);
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
songListHeader = LayoutInflater.from(this).inflate(R.layout.artist_detail_header, songListView, false);
|
||||
albumRecyclerView = songListHeader.findViewById(R.id.recycler_view);
|
||||
}
|
||||
|
||||
private void setUpViews() {
|
||||
setUpSongListView();
|
||||
setUpAlbumRecyclerView();
|
||||
setColors(DialogUtils.resolveColor(this, R.attr.defaultFooterColor));
|
||||
}
|
||||
|
||||
private void setUpSongListView() {
|
||||
setUpSongListPadding();
|
||||
songListView.setScrollViewCallbacks(observableScrollViewCallbacks);
|
||||
songListView.addHeaderView(songListHeader);
|
||||
|
||||
songAdapter = new ArtistSongAdapter(this, getArtist().getSongs(), this);
|
||||
songListView.setAdapter(songAdapter);
|
||||
|
||||
final View contentView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||
contentView.post(() -> observableScrollViewCallbacks.onScrollChanged(-headerViewHeight, false, false));
|
||||
}
|
||||
|
||||
private void setUpSongListPadding() {
|
||||
songListView.setPadding(0, headerViewHeight, 0, 0);
|
||||
}
|
||||
|
||||
private void setUpAlbumRecyclerView() {
|
||||
albumRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
||||
albumAdapter = new HorizontalAlbumAdapter(this, getArtist().albums, usePalette, this);
|
||||
albumRecyclerView.setAdapter(albumAdapter);
|
||||
albumAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
if (albumAdapter.getItemCount() == 0) finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void setUsePalette(boolean usePalette) {
|
||||
albumAdapter.usePalette(usePalette);
|
||||
PreferenceUtil.getInstance(this).setAlbumArtistColoredFooters(usePalette);
|
||||
this.usePalette = usePalette;
|
||||
}
|
||||
|
||||
private void loadArtistImage(String primary) {
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(this), primary)
|
||||
.generatePalette(this).build()
|
||||
.listener(new RequestListener<Object, BitmapPaletteWrapper>() {
|
||||
@Override
|
||||
public boolean onException(Exception e, Object model, Target<BitmapPaletteWrapper> target, boolean isFirstResource) {
|
||||
if (Build.VERSION.SDK_INT > 21) startPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(BitmapPaletteWrapper resource, Object model, Target<BitmapPaletteWrapper> target, boolean dataSource, boolean isFirstResource) {
|
||||
if (Build.VERSION.SDK_INT > 21) startPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.dontAnimate()
|
||||
.into(new CustomPaletteTarget(artistImage) {
|
||||
@Override
|
||||
public void onColorReady(int color) {
|
||||
setColors(color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPaletteColor() {
|
||||
return toolbarColor;
|
||||
}
|
||||
|
||||
private void setColors(int color) {
|
||||
toolbarColor = color;
|
||||
headerView.setBackgroundColor(color);
|
||||
|
||||
setNavigationbarColor(color);
|
||||
setTaskDescriptionColor(color);
|
||||
|
||||
toolbar.setBackgroundColor(color);
|
||||
// needed to auto readjust the toolbar content color
|
||||
setSupportActionBar(toolbar);
|
||||
setStatusbarColor(color);
|
||||
|
||||
int secondaryTextColor = MaterialValueHelper.getSecondaryTextColor(this, ColorUtil.isColorLight(color));
|
||||
durationIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
songCountIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
albumCountIconImageView.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
|
||||
durationTextView.setTextColor(secondaryTextColor);
|
||||
songCountTextView.setTextColor(secondaryTextColor);
|
||||
albumCountTextView.setTextColor(secondaryTextColor);
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
setSupportActionBar(toolbar);
|
||||
// noinspection ConstantConditions
|
||||
getSupportActionBar().setTitle(null);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_artist_detail, menu);
|
||||
menu.findItem(R.id.action_colored_footers).setChecked(usePalette);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final List<Song> songs = songAdapter.getDataSet();
|
||||
switch (id) {
|
||||
case R.id.action_sleep_timer:
|
||||
new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
|
||||
return true;
|
||||
case R.id.action_shuffle_artist:
|
||||
MusicPlayerRemote.openAndShuffleQueue(songs, true);
|
||||
return true;
|
||||
case R.id.action_play_next:
|
||||
MusicPlayerRemote.playNext(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_queue:
|
||||
MusicPlayerRemote.enqueue(songs);
|
||||
return true;
|
||||
case R.id.action_add_to_playlist:
|
||||
AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST");
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
super.onBackPressed();
|
||||
return true;
|
||||
case R.id.action_colored_footers:
|
||||
item.setChecked(!item.isChecked());
|
||||
setUsePalette(item.isChecked());
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MaterialCab openCab(int menuRes, @NonNull final MaterialCab.Callback callback) {
|
||||
if (cab != null && cab.isActive()) cab.finish();
|
||||
cab = new MaterialCab(this, R.id.cab_stub)
|
||||
.setMenu(menuRes)
|
||||
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
|
||||
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(getPaletteColor()))
|
||||
.start(new MaterialCab.Callback() {
|
||||
@Override
|
||||
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
|
||||
return callback.onCabCreated(materialCab, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabItemClicked(MenuItem menuItem) {
|
||||
return callback.onCabItemClicked(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCabFinished(MaterialCab materialCab) {
|
||||
return callback.onCabFinished(materialCab);
|
||||
}
|
||||
});
|
||||
return cab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (cab != null && cab.isActive()) {
|
||||
cab.finish();
|
||||
} else {
|
||||
albumRecyclerView.stopScroll();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
super.onMediaStoreChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusbarColor(int color) {
|
||||
super.setStatusbarColor(color);
|
||||
setLightStatusbar(false);
|
||||
}
|
||||
|
||||
private void setArtist(Artist artist) {
|
||||
this.artist = artist;
|
||||
|
||||
getSupportActionBar().setTitle(artist.getName());
|
||||
songCountTextView.setText(MusicUtil.getSongCountString(this, artist.getSongCount()));
|
||||
albumCountTextView.setText(MusicUtil.getAlbumCountString(this, artist.getAlbumCount()));
|
||||
durationTextView.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, artist.getSongs())));
|
||||
|
||||
// TODO this activity will crash when an artist is passed with an empty album array
|
||||
// something in the album adapter is causing the issue
|
||||
if (artist.albums.size() != 0) songAdapter.swapDataSet(artist.songs);
|
||||
if (artist.albums.size() != 0) albumAdapter.swapDataSet(artist.albums);
|
||||
}
|
||||
|
||||
private Artist getArtist() {
|
||||
if (artist == null) artist = new Artist();
|
||||
return artist;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.song.SongAdapter;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.model.Genre;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import org.jellyfin.apiclient.model.querying.ItemQuery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class GenreDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder {
|
||||
public static final String EXTRA_GENRE = "extra_genre";
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
RecyclerView recyclerView;
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(android.R.id.empty)
|
||||
TextView empty;
|
||||
|
||||
private Genre genre;
|
||||
|
||||
private MaterialCab cab;
|
||||
private SongAdapter adapter;
|
||||
|
||||
private RecyclerView.Adapter wrappedAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
genre = getIntent().getExtras().getParcelable(EXTRA_GENRE);
|
||||
|
||||
setUpRecyclerView();
|
||||
setUpToolBar();
|
||||
|
||||
ItemQuery query = new ItemQuery();
|
||||
query.setGenreIds(new String[]{genre.id});
|
||||
QueryUtil.getSongs(query, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
adapter.getDataSet().addAll((List<Song>) media);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createContentView() {
|
||||
return wrapSlidingMusicPanel(R.layout.activity_genre_detail);
|
||||
}
|
||||
|
||||
private void setUpRecyclerView() {
|
||||
ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(this));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
adapter = new SongAdapter(this, new ArrayList<>(), R.layout.item_list, false, this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
checkIsEmpty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpToolBar() {
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setTitle(genre.name);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_genre_detail, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.action_shuffle_genre:
|
||||
MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true);
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) {
|
||||
if (cab != null && cab.isActive()) cab.finish();
|
||||
cab = new MaterialCab(this, R.id.cab_stub)
|
||||
.setMenu(menu)
|
||||
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
|
||||
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
|
||||
.start(callback);
|
||||
|
||||
return cab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (cab != null && cab.isActive()) cab.finish();
|
||||
else {
|
||||
recyclerView.stopScroll();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
super.onMediaStoreChanged();
|
||||
}
|
||||
|
||||
private void checkIsEmpty() {
|
||||
empty.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (recyclerView != null) {
|
||||
recyclerView.setAdapter(null);
|
||||
recyclerView = null;
|
||||
}
|
||||
|
||||
if (wrappedAdapter != null) {
|
||||
WrapperAdapterUtils.releaseAll(wrappedAdapter);
|
||||
wrappedAdapter = null;
|
||||
}
|
||||
|
||||
adapter = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsBaseActivity;
|
||||
|
||||
import org.jellyfin.apiclient.interaction.AndroidCredentialProvider;
|
||||
import org.jellyfin.apiclient.interaction.ConnectionResult;
|
||||
import org.jellyfin.apiclient.interaction.Response;
|
||||
import org.jellyfin.apiclient.interaction.VolleyHttpClient;
|
||||
import org.jellyfin.apiclient.interaction.connectionmanager.ConnectionManager;
|
||||
import org.jellyfin.apiclient.interaction.http.IAsyncHttpClient;
|
||||
import org.jellyfin.apiclient.logging.AndroidLogger;
|
||||
import org.jellyfin.apiclient.model.apiclient.ServerCredentials;
|
||||
import org.jellyfin.apiclient.model.logging.ILogger;
|
||||
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
|
||||
import org.jellyfin.apiclient.model.serialization.IJsonSerializer;
|
||||
import org.jellyfin.apiclient.model.users.AuthenticationResult;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class LoginActivity extends AbsBaseActivity implements View.OnClickListener {
|
||||
public static final String TAG = SplashActivity.class.getSimpleName();
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(R.id.username)
|
||||
EditText username;
|
||||
@BindView(R.id.password)
|
||||
EditText password;
|
||||
@BindView(R.id.server)
|
||||
EditText server;
|
||||
@BindView(R.id.login)
|
||||
Button login;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_login);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
setUpViews();
|
||||
}
|
||||
|
||||
private void setUpViews() {
|
||||
setUpToolbar();
|
||||
setUpOnClickListeners();
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
private void setUpOnClickListeners() {
|
||||
login.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == login) {
|
||||
final Context context = this;
|
||||
IJsonSerializer jsonSerializer = new GsonJsonSerializer();
|
||||
ILogger logger = new AndroidLogger(TAG);
|
||||
IAsyncHttpClient httpClient = new VolleyHttpClient(logger, this);
|
||||
|
||||
AndroidCredentialProvider credentialProvider = new AndroidCredentialProvider(jsonSerializer, this, logger);
|
||||
ConnectionManager connectionManager = App.getConnectionManager(context, jsonSerializer, logger, httpClient);
|
||||
connectionManager.Connect(server.getText().toString(), new Response<ConnectionResult>() {
|
||||
@Override
|
||||
public void onResponse(ConnectionResult result) {
|
||||
App.setApiClient(result.getApiClient());
|
||||
ServerCredentials serverCredentials = new ServerCredentials();
|
||||
serverCredentials.AddOrUpdateServer(result.getServers().get(0));
|
||||
App.getApiClient().AuthenticateUserAsync(username.getText().toString(), password.getText().toString(), new Response<AuthenticationResult>() {
|
||||
@Override
|
||||
public void onResponse(AuthenticationResult result) {
|
||||
if (result.getAccessToken() == null) return;
|
||||
serverCredentials.GetServer(result.getServerId()).setAccessToken(result.getAccessToken());
|
||||
credentialProvider.SaveCredentials(serverCredentials);
|
||||
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.kabouzeid.appthemehelper.util.ATHUtil;
|
||||
import com.kabouzeid.appthemehelper.util.NavigationViewUtil;
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||
import com.dkanada.gramophone.ui.fragments.mainactivity.library.LibraryFragment;
|
||||
import com.dkanada.gramophone.util.MusicUtil;
|
||||
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
|
||||
|
||||
import org.jellyfin.apiclient.interaction.EmptyResponse;
|
||||
import org.jellyfin.apiclient.interaction.VolleyHttpClient;
|
||||
import org.jellyfin.apiclient.interaction.http.IAsyncHttpClient;
|
||||
import org.jellyfin.apiclient.logging.AndroidLogger;
|
||||
import org.jellyfin.apiclient.model.dto.BaseItemDto;
|
||||
import org.jellyfin.apiclient.model.logging.ILogger;
|
||||
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
|
||||
import org.jellyfin.apiclient.model.serialization.IJsonSerializer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MainActivity extends AbsSlidingMusicPanelActivity {
|
||||
public static final String TAG = MainActivity.class.getSimpleName();
|
||||
|
||||
@BindView(R.id.navigation_view)
|
||||
NavigationView navigationView;
|
||||
@BindView(R.id.drawer_layout)
|
||||
DrawerLayout drawerLayout;
|
||||
|
||||
@Nullable
|
||||
MainActivityFragmentCallbacks currentFragment;
|
||||
|
||||
@Nullable
|
||||
private View navigationDrawerHeader;
|
||||
|
||||
@Nullable
|
||||
private List<BaseItemDto> libraries;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
||||
navigationView.setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
Menu menu = navigationView.getMenu();
|
||||
QueryUtil.getLibraries(new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
libraries = (List<BaseItemDto>) media;
|
||||
menu.clear();
|
||||
|
||||
for (BaseItemDto itemDto : libraries) {
|
||||
if (menu.size() == 0) {
|
||||
QueryUtil.currentLibrary = itemDto;
|
||||
}
|
||||
|
||||
if (itemDto.getCollectionType() == null || !itemDto.getCollectionType().equals("music")) continue;
|
||||
menu.add(R.id.navigation_drawer_menu_category_sections, itemDto.getId().hashCode(), menu.size(), itemDto.getName());
|
||||
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_album_white_24dp);
|
||||
}
|
||||
|
||||
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_settings, menu.size(), R.string.action_settings);
|
||||
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_settings_white_24dp);
|
||||
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_about, menu.size(), R.string.action_about);
|
||||
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_info_outline_white_24dp);
|
||||
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_logout, menu.size(), R.string.logout);
|
||||
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_exit_to_app_white_48dp);
|
||||
|
||||
setUpDrawerLayout();
|
||||
|
||||
menu.getItem(0).setChecked(true);
|
||||
if (savedInstanceState == null) {
|
||||
setCurrentFragment(LibraryFragment.newInstance());
|
||||
} else {
|
||||
restoreCurrentFragment();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setCurrentFragment(Fragment fragment) {
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, null).commit();
|
||||
currentFragment = (MainActivityFragmentCallbacks) fragment;
|
||||
}
|
||||
|
||||
private void restoreCurrentFragment() {
|
||||
currentFragment = (MainActivityFragmentCallbacks) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createContentView() {
|
||||
@SuppressLint("InflateParams")
|
||||
View contentView = getLayoutInflater().inflate(R.layout.activity_main_drawer_layout, null);
|
||||
ViewGroup drawerContent = contentView.findViewById(R.id.drawer_content_container);
|
||||
drawerContent.addView(wrapSlidingMusicPanel(R.layout.activity_main_content));
|
||||
return contentView;
|
||||
}
|
||||
|
||||
private void setUpNavigationView() {
|
||||
int accentColor = ThemeStore.accentColor(this);
|
||||
NavigationViewUtil.setItemIconColors(navigationView, ATHUtil.resolveColor(this, R.attr.iconColor, ThemeStore.textColorSecondary(this)), accentColor);
|
||||
NavigationViewUtil.setItemTextColors(navigationView, ThemeStore.textColorPrimary(this), accentColor);
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(menuItem -> {
|
||||
drawerLayout.closeDrawers();
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.nav_settings:
|
||||
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, SettingsActivity.class)), 200);
|
||||
break;
|
||||
case R.id.nav_about:
|
||||
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, AboutActivity.class)), 200);
|
||||
break;
|
||||
case R.id.nav_logout:
|
||||
IJsonSerializer jsonSerializer = new GsonJsonSerializer();
|
||||
ILogger logger = new AndroidLogger(TAG);
|
||||
IAsyncHttpClient httpClient = new VolleyHttpClient(logger, this);
|
||||
|
||||
App.getConnectionManager(this, jsonSerializer, logger, httpClient).Logout(new EmptyResponse());
|
||||
Intent intent = new Intent(this, LoginActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
this.startActivity(intent);
|
||||
}
|
||||
|
||||
// only run the following code when a new library has been selected
|
||||
if (menuItem.getItemId() == QueryUtil.currentLibrary.getId().hashCode()) return true;
|
||||
for (BaseItemDto itemDto : libraries) {
|
||||
if (menuItem.getItemId() == itemDto.getId().hashCode()) {
|
||||
QueryUtil.currentLibrary = itemDto;
|
||||
setCurrentFragment(LibraryFragment.newInstance());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// setCheckable must be applied to the items on creation
|
||||
// it also applies a tacky background color for the checked item
|
||||
// this is a hack to check the current item without that
|
||||
if (menuItem.getItemId() == R.id.nav_settings || menuItem.getItemId() == R.id.nav_about) return true;
|
||||
for (int i = 0; i < navigationView.getMenu().size(); i++) {
|
||||
if (navigationView.getMenu().getItem(i) == menuItem) {
|
||||
navigationView.getMenu().getItem(i).setChecked(true);
|
||||
} else {
|
||||
navigationView.getMenu().getItem(i).setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpDrawerLayout() {
|
||||
setUpNavigationView();
|
||||
}
|
||||
|
||||
private void updateNavigationDrawerHeader() {
|
||||
if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) {
|
||||
Song song = MusicPlayerRemote.getCurrentSong();
|
||||
if (navigationDrawerHeader == null) {
|
||||
navigationDrawerHeader = navigationView.inflateHeaderView(R.layout.navigation_drawer_header);
|
||||
navigationDrawerHeader.setOnClickListener(v -> {
|
||||
drawerLayout.closeDrawers();
|
||||
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
|
||||
expandPanel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
((TextView) navigationDrawerHeader.findViewById(R.id.title)).setText(song.title);
|
||||
((TextView) navigationDrawerHeader.findViewById(R.id.text)).setText(MusicUtil.getSongInfoString(song));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(Glide.with(this), song.primary)
|
||||
.build().into(((ImageView) navigationDrawerHeader.findViewById(R.id.image)));
|
||||
} else {
|
||||
if (navigationDrawerHeader != null) {
|
||||
navigationView.removeHeaderView(navigationDrawerHeader);
|
||||
navigationDrawerHeader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged();
|
||||
updateNavigationDrawerHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected() {
|
||||
super.onServiceConnected();
|
||||
updateNavigationDrawerHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
if (drawerLayout.isDrawerOpen(navigationView)) {
|
||||
drawerLayout.closeDrawer(navigationView);
|
||||
} else {
|
||||
drawerLayout.openDrawer(navigationView);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleBackPress() {
|
||||
if (drawerLayout.isDrawerOpen(navigationView)) {
|
||||
drawerLayout.closeDrawers();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.handleBackPress() || (currentFragment != null && currentFragment.handleBackPress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelExpanded(View view) {
|
||||
super.onPanelExpanded(view);
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelCollapsed(View view) {
|
||||
super.onPanelCollapsed(view);
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||
}
|
||||
|
||||
public interface MainActivityFragmentCallbacks {
|
||||
boolean handleBackPress();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
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.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.song.OrderablePlaylistSongAdapter;
|
||||
import com.dkanada.gramophone.adapter.song.PlaylistSongAdapter;
|
||||
import com.dkanada.gramophone.adapter.song.SongAdapter;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.helper.menu.PlaylistMenuHelper;
|
||||
import com.dkanada.gramophone.interfaces.CabHolder;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.model.Playlist;
|
||||
import com.dkanada.gramophone.model.PlaylistSong;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.PlaylistUtil;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
|
||||
|
||||
import org.jellyfin.apiclient.model.playlists.PlaylistItemQuery;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class PlaylistDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder {
|
||||
|
||||
@NonNull
|
||||
public static String EXTRA_PLAYLIST = "extra_playlist";
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
RecyclerView recyclerView;
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
@BindView(android.R.id.empty)
|
||||
TextView empty;
|
||||
|
||||
private Playlist playlist;
|
||||
|
||||
private MaterialCab cab;
|
||||
private SongAdapter adapter;
|
||||
|
||||
private RecyclerView.Adapter wrappedAdapter;
|
||||
private RecyclerViewDragDropManager recyclerViewDragDropManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
playlist = getIntent().getExtras().getParcelable(EXTRA_PLAYLIST);
|
||||
|
||||
setUpRecyclerView();
|
||||
setUpToolbar();
|
||||
|
||||
PlaylistItemQuery query = new PlaylistItemQuery();
|
||||
query.setId(playlist.id);
|
||||
PlaylistUtil.getPlaylist(query, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
adapter.getDataSet().addAll((List<PlaylistSong>) media);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createContentView() {
|
||||
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail);
|
||||
}
|
||||
|
||||
private void setUpRecyclerView() {
|
||||
ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(this));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
recyclerViewDragDropManager = new RecyclerViewDragDropManager();
|
||||
final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator();
|
||||
adapter = new OrderablePlaylistSongAdapter(this, new ArrayList<>(), R.layout.item_list, false, this, (fromPosition, toPosition) -> {
|
||||
PlaylistUtil.moveItem(playlist.id, (PlaylistSong) adapter.getDataSet().get(fromPosition), toPosition);
|
||||
Song song = adapter.getDataSet().remove(fromPosition);
|
||||
adapter.getDataSet().add(toPosition, song);
|
||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||
});
|
||||
|
||||
wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(adapter);
|
||||
|
||||
recyclerView.setAdapter(wrappedAdapter);
|
||||
recyclerView.setItemAnimator(animator);
|
||||
|
||||
recyclerViewDragDropManager.attachRecyclerView(recyclerView);
|
||||
|
||||
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
checkIsEmpty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setToolbarTitle(playlist.name);
|
||||
}
|
||||
|
||||
private void setToolbarTitle(String title) {
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.action_shuffle_playlist:
|
||||
MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true);
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return PlaylistMenuHelper.handleMenuClick(this, playlist, item);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) {
|
||||
if (cab != null && cab.isActive()) cab.finish();
|
||||
cab = new MaterialCab(this, R.id.cab_stub)
|
||||
.setMenu(menu)
|
||||
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
|
||||
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
|
||||
.start(callback);
|
||||
|
||||
return cab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (cab != null && cab.isActive()) {
|
||||
cab.finish();
|
||||
} else {
|
||||
recyclerView.stopScroll();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
super.onMediaStoreChanged();
|
||||
}
|
||||
|
||||
private void checkIsEmpty() {
|
||||
empty.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (recyclerViewDragDropManager != null) {
|
||||
recyclerViewDragDropManager.cancelDrag();
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (recyclerViewDragDropManager != null) {
|
||||
recyclerViewDragDropManager.release();
|
||||
recyclerViewDragDropManager = null;
|
||||
}
|
||||
|
||||
if (recyclerView != null) {
|
||||
recyclerView.setItemAnimator(null);
|
||||
recyclerView.setAdapter(null);
|
||||
recyclerView = null;
|
||||
}
|
||||
|
||||
if (wrappedAdapter != null) {
|
||||
WrapperAdapterUtils.releaseAll(wrappedAdapter);
|
||||
wrappedAdapter = null;
|
||||
}
|
||||
|
||||
adapter = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.adapter.SearchAdapter;
|
||||
import com.dkanada.gramophone.interfaces.MediaCallback;
|
||||
import com.dkanada.gramophone.model.Album;
|
||||
import com.dkanada.gramophone.model.Artist;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsMusicServiceActivity;
|
||||
import com.dkanada.gramophone.util.QueryUtil;
|
||||
import com.dkanada.gramophone.util.Util;
|
||||
|
||||
import org.jellyfin.apiclient.model.querying.ItemQuery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SearchActivity extends AbsMusicServiceActivity implements SearchView.OnQueryTextListener {
|
||||
|
||||
public static final String QUERY = "query";
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@BindView(android.R.id.empty)
|
||||
TextView empty;
|
||||
|
||||
SearchView searchView;
|
||||
|
||||
private Handler handler;
|
||||
|
||||
private SearchAdapter adapter;
|
||||
private String query;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_search);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
adapter = new SearchAdapter(this, Collections.emptyList());
|
||||
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
empty.setVisibility(adapter.getItemCount() < 1 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setOnTouchListener((v, event) -> {
|
||||
hideSoftKeyboard();
|
||||
return false;
|
||||
});
|
||||
|
||||
setUpToolBar();
|
||||
|
||||
handler = new Handler();
|
||||
if (savedInstanceState != null) {
|
||||
query = savedInstanceState.getString(QUERY);
|
||||
search(query);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(QUERY, query);
|
||||
}
|
||||
|
||||
private void setUpToolBar() {
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
// noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_search, menu);
|
||||
|
||||
final MenuItem searchItem = menu.findItem(R.id.search);
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setQueryHint(getString(R.string.action_search));
|
||||
searchView.setMaxWidth(Integer.MAX_VALUE);
|
||||
|
||||
searchItem.expandActionView();
|
||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
onBackPressed();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setQuery(query, false);
|
||||
searchView.post(() -> searchView.setOnQueryTextListener(SearchActivity.this));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void search(@NonNull String query) {
|
||||
this.query = query;
|
||||
ItemQuery itemQuery = new ItemQuery();
|
||||
itemQuery.setSearchTerm(query);
|
||||
QueryUtil.getItems(itemQuery, new MediaCallback() {
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
Collections.sort(media, new Comparator<Object>() {
|
||||
public int compare(Object one, Object two) {
|
||||
if (one.getClass() == Album.class || one.getClass() == Artist.class) {
|
||||
if (two.getClass() == Song.class) return -1;
|
||||
}
|
||||
|
||||
if (two.getClass() == Album.class || two.getClass() == Artist.class) {
|
||||
if (one.getClass() == Song.class) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
adapter.swapDataSet((List<Object>) media);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
super.onMediaStoreChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
hideSoftKeyboard();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
search(newText);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void hideSoftKeyboard() {
|
||||
Util.hideSoftKeyboard(SearchActivity.this);
|
||||
if (searchView != null) {
|
||||
searchView.clearFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.afollestad.materialdialogs.color.ColorChooserDialog;
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEColorPreference;
|
||||
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.shortcuts.DynamicShortcutManager;
|
||||
import com.dkanada.gramophone.preferences.LibraryPreference;
|
||||
import com.dkanada.gramophone.preferences.LibraryPreferenceDialog;
|
||||
import com.dkanada.gramophone.preferences.NowPlayingScreenPreference;
|
||||
import com.dkanada.gramophone.preferences.NowPlayingScreenPreferenceDialog;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsBaseActivity;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SettingsActivity extends AbsBaseActivity implements ColorChooserDialog.ColorCallback {
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_preferences);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new SettingsFragment()).commit();
|
||||
} else {
|
||||
SettingsFragment frag = (SettingsFragment) getSupportFragmentManager().findFragmentById(R.id.content_frame);
|
||||
if (frag != null) frag.invalidateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) {
|
||||
switch (dialog.getTitle()) {
|
||||
case R.string.primary_color:
|
||||
ThemeStore.editTheme(this).primaryColor(selectedColor).commit();
|
||||
break;
|
||||
case R.string.accent_color:
|
||||
ThemeStore.editTheme(this).accentColor(selectedColor).commit();
|
||||
break;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
new DynamicShortcutManager(this).updateDynamicShortcuts();
|
||||
}
|
||||
recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorChooserDismissed(@NonNull ColorChooserDialog dialog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends ATEPreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static void setSummary(@NonNull Preference preference) {
|
||||
setSummary(preference, PreferenceManager
|
||||
.getDefaultSharedPreferences(preference.getContext())
|
||||
.getString(preference.getKey(), ""));
|
||||
}
|
||||
|
||||
private static void setSummary(Preference preference, @NonNull Object value) {
|
||||
String stringValue = value.toString();
|
||||
|
||||
if (preference instanceof ListPreference) {
|
||||
ListPreference listPreference = (ListPreference) preference;
|
||||
int index = listPreference.findIndexOfValue(stringValue);
|
||||
preference.setSummary(
|
||||
index >= 0
|
||||
? listPreference.getEntries()[index]
|
||||
: null);
|
||||
} else {
|
||||
preference.setSummary(stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s) {
|
||||
addPreferencesFromResource(R.xml.pref_library);
|
||||
addPreferencesFromResource(R.xml.pref_colors);
|
||||
addPreferencesFromResource(R.xml.pref_notification);
|
||||
addPreferencesFromResource(R.xml.pref_now_playing_screen);
|
||||
addPreferencesFromResource(R.xml.pref_lockscreen);
|
||||
addPreferencesFromResource(R.xml.pref_audio);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DialogFragment onCreatePreferenceDialog(Preference preference) {
|
||||
if (preference instanceof NowPlayingScreenPreference) {
|
||||
return NowPlayingScreenPreferenceDialog.newInstance();
|
||||
} else if (preference instanceof LibraryPreference) {
|
||||
return LibraryPreferenceDialog.newInstance();
|
||||
}
|
||||
return super.onCreatePreferenceDialog(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
getListView().setPadding(0, 0, 0, 0);
|
||||
invalidateSettings();
|
||||
PreferenceUtil.getInstance(getActivity()).registerOnSharedPreferenceChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
PreferenceUtil.getInstance(getActivity()).unregisterOnSharedPreferenceChangedListener(this);
|
||||
}
|
||||
|
||||
private void invalidateSettings() {
|
||||
final Preference generalTheme = findPreference("general_theme");
|
||||
setSummary(generalTheme);
|
||||
generalTheme.setOnPreferenceChangeListener((preference, o) -> {
|
||||
String themeName = (String) o;
|
||||
setSummary(generalTheme, o);
|
||||
|
||||
ThemeStore.markChanged(getActivity());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
// Set the new theme so that updateAppShortcuts can pull it
|
||||
getActivity().setTheme(PreferenceUtil.getThemeResFromPrefValue(themeName));
|
||||
new DynamicShortcutManager(getActivity()).updateDynamicShortcuts();
|
||||
}
|
||||
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
|
||||
final ATEColorPreference primaryColorPref = (ATEColorPreference) findPreference("primary_color");
|
||||
final int primaryColor = ThemeStore.primaryColor(getActivity());
|
||||
primaryColorPref.setColor(primaryColor, ColorUtil.darkenColor(primaryColor));
|
||||
primaryColorPref.setOnPreferenceClickListener(preference -> {
|
||||
new ColorChooserDialog.Builder(getActivity(), R.string.primary_color)
|
||||
.accentMode(false)
|
||||
.allowUserColorInput(true)
|
||||
.allowUserColorInputAlpha(false)
|
||||
.preselect(primaryColor)
|
||||
.show(getActivity());
|
||||
return true;
|
||||
});
|
||||
|
||||
final ATEColorPreference accentColorPref = (ATEColorPreference) findPreference("accent_color");
|
||||
final int accentColor = ThemeStore.accentColor(getActivity());
|
||||
accentColorPref.setColor(accentColor, ColorUtil.darkenColor(accentColor));
|
||||
accentColorPref.setOnPreferenceClickListener(preference -> {
|
||||
new ColorChooserDialog.Builder(getActivity(), R.string.accent_color)
|
||||
.accentMode(true)
|
||||
.allowUserColorInput(true)
|
||||
.allowUserColorInputAlpha(false)
|
||||
.preselect(accentColor)
|
||||
.show(getActivity());
|
||||
return true;
|
||||
});
|
||||
|
||||
TwoStatePreference colorNavBar = (TwoStatePreference) findPreference("should_color_navigation_bar");
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
colorNavBar.setVisible(false);
|
||||
} else {
|
||||
colorNavBar.setChecked(ThemeStore.coloredNavigationBar(getActivity()));
|
||||
colorNavBar.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
ThemeStore.editTheme(getActivity())
|
||||
.coloredNavigationBar((Boolean) newValue)
|
||||
.commit();
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final TwoStatePreference classicNotification = (TwoStatePreference) findPreference("classic_notification");
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
classicNotification.setVisible(false);
|
||||
} else {
|
||||
classicNotification.setChecked(PreferenceUtil.getInstance(getActivity()).getClassicNotification());
|
||||
classicNotification.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// Save preference
|
||||
PreferenceUtil.getInstance(getActivity()).setClassicNotification((Boolean) newValue);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final TwoStatePreference coloredNotification = (TwoStatePreference) findPreference("colored_notification");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
coloredNotification.setEnabled(PreferenceUtil.getInstance(getActivity()).getClassicNotification());
|
||||
} else {
|
||||
coloredNotification.setChecked(PreferenceUtil.getInstance(getActivity()).getColoredNotification());
|
||||
coloredNotification.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// Save preference
|
||||
PreferenceUtil.getInstance(getActivity()).setColoredNotification((Boolean) newValue);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final TwoStatePreference colorAppShortcuts = (TwoStatePreference) findPreference("should_color_app_shortcuts");
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
colorAppShortcuts.setVisible(false);
|
||||
} else {
|
||||
colorAppShortcuts.setChecked(PreferenceUtil.getInstance(getActivity()).getColoredShortcuts());
|
||||
colorAppShortcuts.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// Save preference
|
||||
PreferenceUtil.getInstance(getActivity()).setColoredShortcuts((Boolean) newValue);
|
||||
|
||||
// Update app shortcuts
|
||||
new DynamicShortcutManager(getActivity()).updateDynamicShortcuts();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
updateNowPlayingScreenSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
switch (key) {
|
||||
case PreferenceUtil.NOW_PLAYING_SCREEN:
|
||||
updateNowPlayingScreenSummary();
|
||||
break;
|
||||
case PreferenceUtil.CLASSIC_NOTIFICATION:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
findPreference("colored_notification").setEnabled(sharedPreferences.getBoolean(key, false));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNowPlayingScreenSummary() {
|
||||
findPreference("now_playing_screen_id").setSummary(PreferenceUtil.getInstance(getActivity()).getNowPlayingScreen().titleRes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.dkanada.gramophone.ui.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.dkanada.gramophone.App;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.ui.activities.base.AbsBaseActivity;
|
||||
|
||||
import org.jellyfin.apiclient.interaction.AndroidCredentialProvider;
|
||||
import org.jellyfin.apiclient.interaction.ConnectionResult;
|
||||
import org.jellyfin.apiclient.interaction.Response;
|
||||
import org.jellyfin.apiclient.interaction.VolleyHttpClient;
|
||||
import org.jellyfin.apiclient.interaction.connectionmanager.ConnectionManager;
|
||||
import org.jellyfin.apiclient.interaction.http.IAsyncHttpClient;
|
||||
import org.jellyfin.apiclient.logging.AndroidLogger;
|
||||
import org.jellyfin.apiclient.model.apiclient.ConnectionState;
|
||||
import org.jellyfin.apiclient.model.logging.ILogger;
|
||||
import org.jellyfin.apiclient.model.serialization.GsonJsonSerializer;
|
||||
import org.jellyfin.apiclient.model.serialization.IJsonSerializer;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SplashActivity extends AbsBaseActivity {
|
||||
public static final String TAG = SplashActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_splash);
|
||||
setDrawUnderStatusbar();
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
IJsonSerializer jsonSerializer = new GsonJsonSerializer();
|
||||
ILogger logger = new AndroidLogger(TAG);
|
||||
IAsyncHttpClient httpClient = new VolleyHttpClient(logger, this);
|
||||
|
||||
AndroidCredentialProvider credentialProvider = new AndroidCredentialProvider(jsonSerializer, this, logger);
|
||||
if (credentialProvider.GetCredentials().getServers().size() == 0) {
|
||||
Intent intent = new Intent(this, LoginActivity.class);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
final Context context = this;
|
||||
ConnectionManager connectionManager = App.getConnectionManager(this, jsonSerializer, logger, httpClient);
|
||||
connectionManager.Connect(credentialProvider.GetCredentials().getServers().get(0), new Response<ConnectionResult>() {
|
||||
@Override
|
||||
public void onResponse(ConnectionResult result) {
|
||||
if (result.getState() != ConnectionState.SignedIn) {
|
||||
Intent intent = new Intent(context, LoginActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
App.setApiClient(result.getApiClient());
|
||||
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.dkanada.gramophone.ui.activities.base;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.dkanada.gramophone.R;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public abstract class AbsBaseActivity extends AbsThemeActivity {
|
||||
public static final int PERMISSION_REQUEST = 100;
|
||||
|
||||
private boolean hadPermissions;
|
||||
private String[] permissions;
|
||||
private String permissionDeniedMessage;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
permissions = getPermissionsToRequest();
|
||||
hadPermissions = hasPermissions();
|
||||
|
||||
setPermissionDeniedMessage(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
if (!hasPermissions()) {
|
||||
requestPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
final boolean hasPermissions = hasPermissions();
|
||||
if (hasPermissions != hadPermissions) {
|
||||
hadPermissions = hasPermissions;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
onHasPermissionsChanged(hasPermissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onHasPermissionsChanged(boolean hasPermissions) {
|
||||
// implemented by sub classes
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_UP) {
|
||||
showOverflowMenu();
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
protected void showOverflowMenu() {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String[] getPermissionsToRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected View getSnackBarContainer() {
|
||||
return getWindow().getDecorView();
|
||||
}
|
||||
|
||||
protected void setPermissionDeniedMessage(String message) {
|
||||
permissionDeniedMessage = message;
|
||||
}
|
||||
|
||||
private String getPermissionDeniedMessage() {
|
||||
return permissionDeniedMessage == null ? getString(R.string.permissions_denied) : permissionDeniedMessage;
|
||||
}
|
||||
|
||||
protected void requestPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
|
||||
requestPermissions(permissions, PERMISSION_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
|
||||
for (String permission : permissions) {
|
||||
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == PERMISSION_REQUEST) {
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(AbsBaseActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
//User has deny from permission dialog
|
||||
Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(),
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.action_grant, view -> requestPermissions())
|
||||
.setActionTextColor(ThemeStore.accentColor(this))
|
||||
.show();
|
||||
} else {
|
||||
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page
|
||||
Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(),
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.action_settings, view -> {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
Uri uri = Uri.fromParts("package", AbsBaseActivity.this.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
startActivity(intent);
|
||||
})
|
||||
.setActionTextColor(ThemeStore.accentColor(this))
|
||||
.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
hadPermissions = true;
|
||||
onHasPermissionsChanged(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
package com.dkanada.gramophone.ui.activities.base;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.interfaces.MusicServiceEventListener;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements MusicServiceEventListener {
|
||||
|
||||
private final List<MusicServiceEventListener> mMusicServiceEventListeners = new ArrayList<>();
|
||||
|
||||
private MusicPlayerRemote.ServiceToken serviceToken;
|
||||
private MusicStateReceiver musicStateReceiver;
|
||||
private boolean receiverRegistered;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
serviceToken = MusicPlayerRemote.bindToService(this, new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
AbsMusicServiceActivity.this.onServiceConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
AbsMusicServiceActivity.this.onServiceDisconnected();
|
||||
}
|
||||
});
|
||||
|
||||
setPermissionDeniedMessage(getString(R.string.permission_external_storage_denied));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
MusicPlayerRemote.unbindFromService(serviceToken);
|
||||
if (receiverRegistered) {
|
||||
unregisterReceiver(musicStateReceiver);
|
||||
receiverRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void addMusicServiceEventListener(final MusicServiceEventListener listener) {
|
||||
if (listener != null) {
|
||||
mMusicServiceEventListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMusicServiceEventListener(final MusicServiceEventListener listener) {
|
||||
if (listener != null) {
|
||||
mMusicServiceEventListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected() {
|
||||
if (!receiverRegistered) {
|
||||
musicStateReceiver = new MusicStateReceiver(this);
|
||||
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(MusicService.PLAY_STATE_CHANGED);
|
||||
filter.addAction(MusicService.SHUFFLE_MODE_CHANGED);
|
||||
filter.addAction(MusicService.REPEAT_MODE_CHANGED);
|
||||
filter.addAction(MusicService.META_CHANGED);
|
||||
filter.addAction(MusicService.QUEUE_CHANGED);
|
||||
filter.addAction(MusicService.MEDIA_STORE_CHANGED);
|
||||
|
||||
registerReceiver(musicStateReceiver, filter);
|
||||
|
||||
receiverRegistered = true;
|
||||
}
|
||||
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onServiceConnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected() {
|
||||
if (receiverRegistered) {
|
||||
unregisterReceiver(musicStateReceiver);
|
||||
receiverRegistered = false;
|
||||
}
|
||||
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onServiceDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayingMetaChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onPlayingMetaChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onQueueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayStateChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onPlayStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaStoreChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onMediaStoreChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onRepeatModeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeChanged() {
|
||||
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
|
||||
if (listener != null) {
|
||||
listener.onShuffleModeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MusicStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private final WeakReference<AbsMusicServiceActivity> reference;
|
||||
|
||||
public MusicStateReceiver(final AbsMusicServiceActivity activity) {
|
||||
reference = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, @NonNull final Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
AbsMusicServiceActivity activity = reference.get();
|
||||
if (activity != null) {
|
||||
switch (action) {
|
||||
case MusicService.META_CHANGED:
|
||||
activity.onPlayingMetaChanged();
|
||||
break;
|
||||
case MusicService.QUEUE_CHANGED:
|
||||
activity.onQueueChanged();
|
||||
break;
|
||||
case MusicService.PLAY_STATE_CHANGED:
|
||||
activity.onPlayStateChanged();
|
||||
break;
|
||||
case MusicService.REPEAT_MODE_CHANGED:
|
||||
activity.onRepeatModeChanged();
|
||||
break;
|
||||
case MusicService.SHUFFLE_MODE_CHANGED:
|
||||
activity.onShuffleModeChanged();
|
||||
break;
|
||||
case MusicService.MEDIA_STORE_CHANGED:
|
||||
activity.onMediaStoreChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHasPermissionsChanged(boolean hasPermissions) {
|
||||
super.onHasPermissionsChanged(hasPermissions);
|
||||
Intent intent = new Intent(MusicService.MEDIA_STORE_CHANGED);
|
||||
intent.putExtra("from_permissions_changed", true); // just in case we need to know this at some point
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected String[] getPermissionsToRequest() {
|
||||
return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
package com.dkanada.gramophone.ui.activities.base;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.helper.MusicPlayerRemote;
|
||||
import com.dkanada.gramophone.ui.fragments.player.AbsPlayerFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.player.MiniPlayerFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.player.NowPlayingScreen;
|
||||
import com.dkanada.gramophone.ui.fragments.player.card.CardPlayerFragment;
|
||||
import com.dkanada.gramophone.ui.fragments.player.flat.FlatPlayerFragment;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.dkanada.gramophone.util.ViewUtil;
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
* <p/>
|
||||
* Do not use {@link #setContentView(int)}. Instead wrap your layout with
|
||||
* {@link #wrapSlidingMusicPanel(int)} first and then return it in {@link #createContentView()}
|
||||
*/
|
||||
public abstract class AbsSlidingMusicPanelActivity extends AbsMusicServiceActivity implements SlidingUpPanelLayout.PanelSlideListener, CardPlayerFragment.Callbacks {
|
||||
|
||||
@BindView(R.id.sliding_layout)
|
||||
SlidingUpPanelLayout slidingUpPanelLayout;
|
||||
|
||||
private int navigationbarColor;
|
||||
private int taskColor;
|
||||
private boolean lightStatusbar;
|
||||
|
||||
private NowPlayingScreen currentNowPlayingScreen;
|
||||
private AbsPlayerFragment playerFragment;
|
||||
private MiniPlayerFragment miniPlayerFragment;
|
||||
|
||||
private ValueAnimator navigationBarColorAnimator;
|
||||
private ArgbEvaluator argbEvaluator = new ArgbEvaluator();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(createContentView());
|
||||
ButterKnife.bind(this);
|
||||
|
||||
currentNowPlayingScreen = PreferenceUtil.getInstance(this).getNowPlayingScreen();
|
||||
Fragment fragment; // must implement AbsPlayerFragment
|
||||
switch (currentNowPlayingScreen) {
|
||||
case FLAT:
|
||||
fragment = new FlatPlayerFragment();
|
||||
break;
|
||||
case CARD:
|
||||
default:
|
||||
fragment = new CardPlayerFragment();
|
||||
break;
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.player_fragment_container, fragment).commit();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
|
||||
playerFragment = (AbsPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.player_fragment_container);
|
||||
miniPlayerFragment = (MiniPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.mini_player_fragment);
|
||||
|
||||
// noinspection ConstantConditions
|
||||
miniPlayerFragment.getView().setOnClickListener(v -> expandPanel());
|
||||
|
||||
slidingUpPanelLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
slidingUpPanelLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
|
||||
switch (getPanelState()) {
|
||||
case EXPANDED:
|
||||
onPanelSlide(slidingUpPanelLayout, 1);
|
||||
onPanelExpanded(slidingUpPanelLayout);
|
||||
break;
|
||||
case COLLAPSED:
|
||||
onPanelCollapsed(slidingUpPanelLayout);
|
||||
break;
|
||||
default:
|
||||
playerFragment.onHide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
slidingUpPanelLayout.addPanelSlideListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (currentNowPlayingScreen != PreferenceUtil.getInstance(this).getNowPlayingScreen()) {
|
||||
postRecreate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAntiDragView(View antiDragView) {
|
||||
slidingUpPanelLayout.setAntiDragView(antiDragView);
|
||||
}
|
||||
|
||||
protected abstract View createContentView();
|
||||
|
||||
@Override
|
||||
public void onServiceConnected() {
|
||||
super.onServiceConnected();
|
||||
if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) {
|
||||
slidingUpPanelLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
slidingUpPanelLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
hideBottomBar(false);
|
||||
}
|
||||
});
|
||||
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChanged() {
|
||||
super.onQueueChanged();
|
||||
hideBottomBar(MusicPlayerRemote.getPlayingQueue().isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelSlide(View panel, @FloatRange(from = 0, to = 1) float slideOffset) {
|
||||
setMiniPlayerAlphaProgress(slideOffset);
|
||||
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
|
||||
super.setNavigationbarColor((int) argbEvaluator.evaluate(slideOffset, navigationbarColor, playerFragment.getPaletteColor()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
|
||||
switch (newState) {
|
||||
case COLLAPSED:
|
||||
onPanelCollapsed(panel);
|
||||
break;
|
||||
case EXPANDED:
|
||||
onPanelExpanded(panel);
|
||||
break;
|
||||
case ANCHORED:
|
||||
collapsePanel(); // this fixes a bug where the panel would get stuck for some reason
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void onPanelCollapsed(View panel) {
|
||||
// restore values
|
||||
super.setLightStatusbar(lightStatusbar);
|
||||
super.setTaskDescriptionColor(taskColor);
|
||||
super.setNavigationbarColor(navigationbarColor);
|
||||
|
||||
playerFragment.setMenuVisibility(false);
|
||||
playerFragment.setUserVisibleHint(false);
|
||||
playerFragment.onHide();
|
||||
}
|
||||
|
||||
public void onPanelExpanded(View panel) {
|
||||
// setting fragments values
|
||||
int playerFragmentColor = playerFragment.getPaletteColor();
|
||||
super.setLightStatusbar(false);
|
||||
super.setTaskDescriptionColor(playerFragmentColor);
|
||||
super.setNavigationbarColor(playerFragmentColor);
|
||||
|
||||
playerFragment.setMenuVisibility(true);
|
||||
playerFragment.setUserVisibleHint(true);
|
||||
playerFragment.onShow();
|
||||
}
|
||||
|
||||
private void setMiniPlayerAlphaProgress(@FloatRange(from = 0, to = 1) float progress) {
|
||||
if (miniPlayerFragment.getView() == null) return;
|
||||
float alpha = 1 - progress;
|
||||
miniPlayerFragment.getView().setAlpha(alpha);
|
||||
// necessary to make the views below clickable
|
||||
miniPlayerFragment.getView().setVisibility(alpha == 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
public SlidingUpPanelLayout.PanelState getPanelState() {
|
||||
return slidingUpPanelLayout == null ? null : slidingUpPanelLayout.getPanelState();
|
||||
}
|
||||
|
||||
public void collapsePanel() {
|
||||
slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
}
|
||||
|
||||
public void expandPanel() {
|
||||
slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
|
||||
}
|
||||
|
||||
public void hideBottomBar(final boolean hide) {
|
||||
if (hide) {
|
||||
slidingUpPanelLayout.setPanelHeight(0);
|
||||
collapsePanel();
|
||||
} else {
|
||||
slidingUpPanelLayout.setPanelHeight(getResources().getDimensionPixelSize(R.dimen.mini_player_height));
|
||||
}
|
||||
}
|
||||
|
||||
protected View wrapSlidingMusicPanel(@LayoutRes int resId) {
|
||||
@SuppressLint("InflateParams")
|
||||
View slidingMusicPanelLayout = getLayoutInflater().inflate(R.layout.sliding_music_panel_layout, null);
|
||||
ViewGroup contentContainer = slidingMusicPanelLayout.findViewById(R.id.content_container);
|
||||
getLayoutInflater().inflate(resId, contentContainer);
|
||||
return slidingMusicPanelLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!handleBackPress())
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
public boolean handleBackPress() {
|
||||
if (slidingUpPanelLayout.getPanelHeight() != 0 && playerFragment.onBackPressed())
|
||||
return true;
|
||||
if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
collapsePanel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaletteColorChanged() {
|
||||
if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
|
||||
int playerFragmentColor = playerFragment.getPaletteColor();
|
||||
super.setTaskDescriptionColor(playerFragmentColor);
|
||||
animateNavigationBarColor(playerFragmentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightStatusbar(boolean enabled) {
|
||||
lightStatusbar = enabled;
|
||||
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
|
||||
super.setLightStatusbar(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNavigationbarColor(int color) {
|
||||
this.navigationbarColor = color;
|
||||
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
|
||||
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
|
||||
super.setNavigationbarColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateNavigationBarColor(int color) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
|
||||
navigationBarColorAnimator = ValueAnimator
|
||||
.ofArgb(getWindow().getNavigationBarColor(), color)
|
||||
.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME);
|
||||
navigationBarColorAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 1f, 1f));
|
||||
navigationBarColorAnimator.addUpdateListener(animation -> AbsSlidingMusicPanelActivity.super.setNavigationbarColor((Integer) animation.getAnimatedValue()));
|
||||
navigationBarColorAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel(); // just in case
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskDescriptionColor(@ColorInt int color) {
|
||||
this.taskColor = color;
|
||||
if (getPanelState() == null || getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
|
||||
super.setTaskDescriptionColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getSnackBarContainer() {
|
||||
return findViewById(R.id.content_container);
|
||||
}
|
||||
|
||||
public SlidingUpPanelLayout getSlidingUpPanelLayout() {
|
||||
return slidingUpPanelLayout;
|
||||
}
|
||||
|
||||
public MiniPlayerFragment getMiniPlayerFragment() {
|
||||
return miniPlayerFragment;
|
||||
}
|
||||
|
||||
public AbsPlayerFragment getPlayerFragment() {
|
||||
return playerFragment;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.dkanada.gramophone.ui.activities.base;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.ColorInt;
|
||||
import android.view.View;
|
||||
|
||||
import com.kabouzeid.appthemehelper.ATH;
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.kabouzeid.appthemehelper.common.ATHToolbarActivity;
|
||||
import com.kabouzeid.appthemehelper.util.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialDialogsUtil;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
import com.dkanada.gramophone.util.Util;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
|
||||
public abstract class AbsThemeActivity extends ATHToolbarActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(PreferenceUtil.getInstance(this).getGeneralTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this);
|
||||
}
|
||||
|
||||
protected void setDrawUnderStatusbar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
Util.setAllowDrawUnderStatusBar(getWindow());
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||
Util.setStatusBarTranslucent(getWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop.
|
||||
* On Lollipop if no such view is found it will set the statusbar color using the native method.
|
||||
*
|
||||
* @param color the new statusbar color (will be shifted down on Lollipop and above)
|
||||
*/
|
||||
public void setStatusbarColor(int color) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
final View statusBar = getWindow().getDecorView().getRootView().findViewById(R.id.status_bar);
|
||||
if (statusBar != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
statusBar.setBackgroundColor(ColorUtil.darkenColor(color));
|
||||
setLightStatusbarAuto(color);
|
||||
} else {
|
||||
statusBar.setBackgroundColor(color);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= 21) {
|
||||
getWindow().setStatusBarColor(ColorUtil.darkenColor(color));
|
||||
setLightStatusbarAuto(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusbarColorAuto() {
|
||||
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
|
||||
setStatusbarColor(ThemeStore.primaryColor(this));
|
||||
}
|
||||
|
||||
public void setTaskDescriptionColor(@ColorInt int color) {
|
||||
ATH.setTaskDescriptionColor(this, color);
|
||||
}
|
||||
|
||||
public void setTaskDescriptionColorAuto() {
|
||||
setTaskDescriptionColor(ThemeStore.primaryColor(this));
|
||||
}
|
||||
|
||||
public void setNavigationbarColor(int color) {
|
||||
if (ThemeStore.coloredNavigationBar(this)) {
|
||||
ATH.setNavigationbarColor(this, color);
|
||||
} else {
|
||||
ATH.setNavigationbarColor(this, Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNavigationbarColorAuto() {
|
||||
setNavigationbarColor(ThemeStore.navigationBarColor(this));
|
||||
}
|
||||
|
||||
public void setLightStatusbar(boolean enabled) {
|
||||
ATH.setLightStatusbar(this, enabled);
|
||||
}
|
||||
|
||||
public void setLightStatusbarAuto(int bgColor) {
|
||||
setLightStatusbar(ColorUtil.isColorLight(bgColor));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue