package com.simplecity.amp_library.ui.fragments; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import com.bumptech.glide.Glide; import com.bumptech.glide.RequestManager; import com.simplecity.amp_library.R; import com.simplecity.amp_library.model.AdaptableItem; import com.simplecity.amp_library.model.Album; import com.simplecity.amp_library.model.AlbumArtist; import com.simplecity.amp_library.model.Playlist; import com.simplecity.amp_library.model.Song; import com.simplecity.amp_library.model.SuggestedHeader; import com.simplecity.amp_library.sql.databases.BlacklistHelper; import com.simplecity.amp_library.ui.adapters.ItemAdapter; import com.simplecity.amp_library.ui.adapters.SuggestedAdapter; import com.simplecity.amp_library.ui.modelviews.AlbumView; import com.simplecity.amp_library.ui.modelviews.EmptyView; import com.simplecity.amp_library.ui.modelviews.HorizontalRecyclerView; import com.simplecity.amp_library.ui.modelviews.SuggestedHeaderView; import com.simplecity.amp_library.ui.modelviews.SuggestedSongView; import com.simplecity.amp_library.ui.modelviews.ViewType; import com.simplecity.amp_library.ui.views.SuggestedDividerDecoration; import com.simplecity.amp_library.utils.ComparisonUtils; import com.simplecity.amp_library.utils.MenuUtils; import com.simplecity.amp_library.utils.MusicUtils; import com.simplecity.amp_library.utils.Operators; import com.simplecity.amp_library.utils.PermissionUtils; import com.simplecity.amp_library.utils.ThemeUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; public class SuggestedFragment extends BaseFragment implements MusicUtils.Defs, SuggestedAdapter.SuggestedListener, RecyclerView.RecyclerListener, HorizontalRecyclerView.HorizontalAdapter.ItemListener { public interface SuggestedClickListener { void onItemClicked(Serializable item, View transitionView); } private static final String TAG = "SuggestedFragment"; private static final String ARG_PAGE_TITLE = "page_title"; private RecyclerView recyclerView; SuggestedAdapter suggestedAdapter; private BroadcastReceiver mReceiver; private SharedPreferences mPrefs; private SharedPreferences.OnSharedPreferenceChangeListener mSharedPreferenceChangeListener; private CompositeSubscription subscription; private RequestManager requestManager; private HorizontalRecyclerView favoriteRecyclerView; private HorizontalRecyclerView mostPlayedRecyclerView; private SuggestedClickListener suggestedClickListener; public SuggestedFragment() { } public static SuggestedFragment newInstance(String pageTitle) { SuggestedFragment fragment = new SuggestedFragment(); Bundle args = new Bundle(); args.putString(ARG_PAGE_TITLE, pageTitle); fragment.setArguments(args); return fragment; } @Override public void onAttach(Context context) { super.onAttach(context); suggestedClickListener = (SuggestedClickListener) getActivity(); } @Override public void onDetach() { super.onDetach(); suggestedClickListener = null; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPrefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction() != null && intent.getAction().equals("restartLoader")) { refreshAdapterItems(); } } }; mSharedPreferenceChangeListener = (sharedPreferences, key) -> { if (key.equals("pref_theme_highlight_color") || key.equals("pref_theme_accent_color") || key.equals("pref_theme_white_accent")) { themeUIComponents(); } else if (key.equals("albumWhitelist")) { refreshAdapterItems(); } }; mPrefs.registerOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener); suggestedAdapter = new SuggestedAdapter(); suggestedAdapter.setListener(this); if (requestManager == null) { requestManager = Glide.with(this); } mostPlayedRecyclerView = new HorizontalRecyclerView(); mostPlayedRecyclerView.setListener(this); favoriteRecyclerView = new HorizontalRecyclerView(); favoriteRecyclerView.setListener(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (recyclerView == null) { recyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_suggested, container, false); recyclerView.addItemDecoration(new SuggestedDividerDecoration(getResources())); recyclerView.setAdapter(suggestedAdapter); recyclerView.setRecyclerListener(this); GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 6); gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (!suggestedAdapter.items.isEmpty() && position >= 0) { AdaptableItem item = suggestedAdapter.items.get(position); if (item instanceof HorizontalRecyclerView || item instanceof SuggestedHeaderView || (item instanceof AlbumView && item.getViewType() == ViewType.ALBUM_LIST) || (item instanceof AlbumView && item.getViewType() == ViewType.ALBUM_LIST_SMALL) || item instanceof EmptyView) { return 6; } if (item instanceof AlbumView && item.getViewType() == ViewType.ALBUM_CARD_LARGE) { return 3; } } return 2; } }); recyclerView.setLayoutManager(gridLayoutManager); themeUIComponents(); } return recyclerView; } private void themeUIComponents() { if (recyclerView != null) { ThemeUtils.themeRecyclerView(recyclerView); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { ThemeUtils.themeRecyclerView(recyclerView); super.onScrollStateChanged(recyclerView, newState); } }); } } @Override public void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(); filter.addAction("restartLoader"); getActivity().registerReceiver(mReceiver, filter); refreshAdapterItems(); } void refreshAdapterItems() { PermissionUtils.RequestStoragePermissions(() -> { if (getActivity() != null && isAdded()) { subscription = new CompositeSubscription(); Observable<Playlist> mostPlayedPlaylistObservable = Observable.fromCallable(Playlist::mostPlayedPlaylist) .subscribeOn(Schedulers.io()) .cache(); Observable<List<Song>> mostPlayedSongsObservable = mostPlayedPlaylistObservable .filter(playlist -> playlist != null) .flatMap(playlist -> playlist.getSongsObservable(getContext())) .cache(); Observable<List<AdaptableItem>> mostPlayedItemsObservable = mostPlayedPlaylistObservable .flatMap(playlist -> { SuggestedHeader mostPlayedHeader = new SuggestedHeader(getString(R.string.mostplayed), getString(R.string.suggested_most_played_songs_subtitle), playlist); SuggestedHeaderView mostPlayedHeaderView = new SuggestedHeaderView(mostPlayedHeader); return mostPlayedSongsObservable .map(songs -> { List<AdaptableItem> items = new ArrayList<>(); if (!songs.isEmpty()) { items.add(mostPlayedHeaderView); items.add(mostPlayedRecyclerView); } return items; }); }) .switchIfEmpty(Observable.just(Collections.emptyList())); Observable<List<AdaptableItem>> recentlyPlayedAlbums = Observable.fromCallable(Playlist::recentlyPlayedPlaylist) .subscribeOn(Schedulers.io()) .filter(playlist -> playlist != null) .flatMap(playlist -> { SuggestedHeader recentlyPlayedHeader = new SuggestedHeader(getString(R.string.suggested_recent_title), getString(R.string.suggested_recent_subtitle), playlist); SuggestedHeaderView recentlyPlayedHeaderView = new SuggestedHeaderView(recentlyPlayedHeader); return playlist.getSongsObservable(getContext()) .flatMap(songs -> Observable.just(Operators.songsToAlbums(songs))) .flatMap(Observable::from) .sorted((a, b) -> ComparisonUtils.compareLong(b.lastPlayed, a.lastPlayed)) .limit(6) .flatMap(album -> //We need to populate the song count album.getSongsObservable() .map(songs -> { album.numSongs = songs.size(); return album; })) .sorted((a, b) -> ComparisonUtils.compareLong(b.lastPlayed, a.lastPlayed)) .filter(album -> album.numSongs > 0) .map(album -> (AdaptableItem) new AlbumView(album, ViewType.ALBUM_LIST_SMALL, requestManager)) .toList() .map(adaptableItems -> { if (!adaptableItems.isEmpty()) { adaptableItems.add(0, recentlyPlayedHeaderView); } return adaptableItems; }); }) .switchIfEmpty(Observable.just(Collections.emptyList())); Observable<Playlist> favouritesPlaylistObservable = Observable.fromCallable(Playlist::favoritesPlaylist) .subscribeOn(Schedulers.io()) .cache(); Observable<List<Song>> favouritesSongsObservable = favouritesPlaylistObservable .filter(playlist -> playlist != null) .flatMap(playlist -> playlist.getSongsObservable(getContext())) .cache(); Observable<List<AdaptableItem>> favoriteSongsItemsObservable = favouritesPlaylistObservable .flatMap(playlist -> { SuggestedHeader favoriteHeader = new SuggestedHeader(getString(R.string.fav_title), getString(R.string.suggested_favorite_subtitle), playlist); SuggestedHeaderView favoriteHeaderView = new SuggestedHeaderView(favoriteHeader); return favouritesSongsObservable .map(songs -> { List<AdaptableItem> items = new ArrayList<>(); if (!songs.isEmpty()) { items.add(favoriteHeaderView); items.add(favoriteRecyclerView); } return items; }); }) .switchIfEmpty(Observable.just(Collections.emptyList())); Observable<List<AdaptableItem>> recentlyAddedAlbums = Observable.fromCallable(Playlist::recentlyAddedPlaylist) .subscribeOn(Schedulers.io()) .filter(playlist -> playlist != null) .flatMap(playlist -> { SuggestedHeader recentlyAddedHeader = new SuggestedHeader(getString(R.string.recentlyadded), getString(R.string.suggested_recently_added_subtitle), playlist); SuggestedHeaderView recentlyAddedHeaderView = new SuggestedHeaderView(recentlyAddedHeader); return playlist.getSongsObservable(getContext()) .flatMap(songs -> Observable.just(Operators.songsToAlbums(songs))) .flatMap(Observable::from) .sorted((a, b) -> ComparisonUtils.compareLong(b.dateAdded, a.dateAdded)) .limit(4) .flatMap(album -> //We need to populate the song count album.getSongsObservable() .map(songs -> { album.numSongs = songs.size(); return album; })) .sorted((a, b) -> ComparisonUtils.compareLong(b.dateAdded, a.dateAdded)) .filter(album -> album.numSongs > 0) .map(album -> (AdaptableItem) new AlbumView(album, ViewType.ALBUM_LIST_SMALL, requestManager)) .toList() .map(adaptableItems -> { if (!adaptableItems.isEmpty()) { adaptableItems.add(0, recentlyAddedHeaderView); } return adaptableItems; }); }) .switchIfEmpty(Observable.just(Collections.emptyList())); Observable.merge(mostPlayedItemsObservable, recentlyPlayedAlbums, favoriteSongsItemsObservable, recentlyAddedAlbums); subscription.add( Observable.combineLatest(mostPlayedItemsObservable, recentlyPlayedAlbums, favoriteSongsItemsObservable, recentlyAddedAlbums, (mostPlayedSongs1, recentlyPlayedAlbums1, favoriteSongs1, recentlyAddedAlbums1) -> { List<AdaptableItem> items = new ArrayList<>(); items.addAll(mostPlayedSongs1); items.addAll(recentlyPlayedAlbums1); items.addAll(favoriteSongs1); items.addAll(recentlyAddedAlbums1); return items; }) .debounce(250, TimeUnit.MILLISECONDS) .switchIfEmpty(Observable.just(new ArrayList<>())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(adaptableItems -> { if (adaptableItems.isEmpty()) { suggestedAdapter.setEmpty(new EmptyView(R.string.empty_suggested)); } else { suggestedAdapter.setItems(adaptableItems); } })); subscription.add(mostPlayedSongsObservable .map(songs -> { Collections.sort(songs, (a, b) -> ComparisonUtils.compareInt(b.playCount, a.playCount)); return Stream.of(songs) .map(song -> (AdaptableItem) new SuggestedSongView(song, requestManager)) .limit(20) .collect(Collectors.toList()); }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(adaptableItems -> { mostPlayedRecyclerView.itemAdapter.setItems(adaptableItems); })); subscription.add(favouritesSongsObservable .map(songs -> Stream.of(songs) .map(song -> (AdaptableItem) new SuggestedSongView(song, requestManager)) .limit(20) .collect(Collectors.toList())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(adaptableItems -> { favoriteRecyclerView.itemAdapter.setItems(adaptableItems); })); } }); } @Override public void onPause() { if (mReceiver != null) { getActivity().unregisterReceiver(mReceiver); } if (subscription != null) { subscription.unsubscribe(); } super.onPause(); } @Override public void onDestroy() { mPrefs.unregisterOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener); super.onDestroy(); } @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { if (holder.getAdapterPosition() != -1) { suggestedAdapter.items.get(holder.getAdapterPosition()).recycle(holder); } } @Override public void onItemClick(ItemAdapter adapter, View v, int position, final Object item) { if (item instanceof Song) { Observable<List<Song>> songsObservable; if (adapter instanceof HorizontalRecyclerView.HorizontalAdapter) { //The user tapped a song belonging to a HorizontalRecyclerView adapter. Play it amongst the //other songs within that adapter. songsObservable = Observable.fromCallable(() -> Stream.of(((HorizontalRecyclerView.HorizontalAdapter) adapter).items) .map(adaptableItem -> (Song) adaptableItem.getItem()) .collect(Collectors.toList())); } else { //Otherwise, play the song amongst other songs from the same album songsObservable = ((Song) item).getAlbum() .getSongsObservable() .map(songs -> { Collections.sort(songs, (a, b) -> ComparisonUtils.compareInt(a.track, b.track)); Collections.sort(songs, (a, b) -> ComparisonUtils.compareInt(a.discNumber, b.discNumber)); return songs; }); } songsObservable.observeOn(AndroidSchedulers.mainThread()) .subscribe(songs -> MusicUtils.playAll(songs, songs.indexOf((Song) item), () -> { final String message = getContext().getString(R.string.emptyplaylist); Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); })); } else { Object model = item; if (suggestedClickListener != null) { if (item instanceof SuggestedHeader) { model = ((SuggestedHeader) item).playlist; } suggestedClickListener.onItemClicked((Serializable) model, v.findViewById(R.id.image)); } } } @Override public void onOverflowClick(View v, int position, Object item) { if (item instanceof AlbumArtist) { PopupMenu menu = new PopupMenu(SuggestedFragment.this.getActivity(), v); MenuUtils.addAlbumArtistMenuOptions(getActivity(), menu); MenuUtils.addClickHandler((AppCompatActivity) getActivity(), menu, (AlbumArtist) item); menu.show(); } else if (item instanceof Album) { PopupMenu menu = new PopupMenu(SuggestedFragment.this.getActivity(), v); MenuUtils.addAlbumMenuOptions(getActivity(), menu); MenuUtils.addClickHandler((AppCompatActivity) getActivity(), menu, (Album) item); menu.show(); } else if (item instanceof Song) { PopupMenu menu = new PopupMenu(SuggestedFragment.this.getActivity(), v); MenuUtils.addSongMenuOptions(getActivity(), menu); MenuUtils.addClickHandler((AppCompatActivity) getActivity(), menu, (Song) item, menuItem -> { switch (menuItem.getItemId()) { case BLACKLIST: { BlacklistHelper.addToBlacklist(((Song) item)); suggestedAdapter.removeItem(position); return true; } } return false; }); menu.show(); } } @Override protected String screenName() { return TAG; } }