Java tutorial
/** * CollectionAdapter.java * Implements the CollectionAdapter class * A CollectionAdapter is a custom adapter for a RecyclerView * <p> * This file is part of * TRANSISTOR - Radio App for Android * <p> * Copyright (c) 2015-17 - Y20K.org * Licensed under the MIT-License * http://opensource.org/licenses/MIT */ package org.y20k.transistor; import android.app.Activity; import android.app.ActivityOptions; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Vibrator; import android.preference.PreferenceManager; import android.support.design.widget.FloatingActionButton; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.util.SortedList; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.util.SortedListAdapterCallback; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageButton; import android.widget.Toast; import org.y20k.transistor.core.Station; import org.y20k.transistor.helpers.DialogError; import org.y20k.transistor.helpers.LogHelper; import org.y20k.transistor.helpers.PlaybackStatus; import org.y20k.transistor.helpers.ShortcutHelper; import org.y20k.transistor.helpers.SingletonProperties; import org.y20k.transistor.helpers.StationContextMenu; import org.y20k.transistor.helpers.TransistorKeys; import org.y20k.transistor.sqlcore.StationsDbHelper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Objects; /** * CollectionAdapter class */ public class CollectionAdapter extends RecyclerView.Adapter<CollectionAdapterViewHolder> { /* Define log tag */ private static final String LOG_TAG = CollectionAdapter.class.getSimpleName(); public static final String LOADING_STATUS = "loading"; public static final String STOP_STATUS = "stop"; public static final String PLAY_STATUS = "play"; /* Main class variables */ private final Activity mActivity; private final File mFolder; private BroadcastReceiver mPlaybackStateChangedReceiver; private boolean mPlayback; private int mStationIDSelected; private boolean mTwoPane; private final SortedList<Station> mStationList; /* Constructor */ public CollectionAdapter(Activity activity, File folder) { // set main variables mActivity = activity; mFolder = folder; mStationIDSelected = 0; mStationList = new SortedList<Station>(Station.class, new SortedListAdapterCallback<Station>(this) { @Override public int compare(Station station1, Station station2) { // Compares two stations: returns "1" if name if this station is greater than name of given station int result = Integer.compare(station2.IS_FAVOURITE, station1.IS_FAVOURITE); if (result == 0) {//equal result = station1.CATEGORY.compareToIgnoreCase(station2.CATEGORY); if (result == 0) { result = station1.TITLE.compareToIgnoreCase(station2.TITLE); } } return result; } @Override public boolean areContentsTheSame(Station oldStation, Station newStation) { return oldStation.StreamURI.equals(newStation.StreamURI); } @Override public boolean areItemsTheSame(Station station1, Station station2) { // return station1.equals(station2); return areContentsTheSame(station1, station2); } }); // fill station list loadCollection(); } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); // load state loadAppState(mActivity); // initialize broadcast receivers initializeBroadcastReceivers(); } @Override public CollectionAdapterViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { // get view View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_collection_v2, parent, false); // put view into holder and return return new CollectionAdapterViewHolder(v); } @Override public void onViewAttachedToWindow(CollectionAdapterViewHolder holder) { super.onViewAttachedToWindow(holder); Log.v(LOG_TAG + "debugCollectionView", "start onViewAttachedToWindow , getFabPlayButton().getTag=" + String.valueOf(holder.getFabPlayButton().getTag())); //restore animation if loading status FloatingActionButton fabPlayButton = holder.getFabPlayButton(); String tag = fabPlayButton.getTag() == null ? "" : fabPlayButton.getTag().toString(); if (Objects.equals(tag, LOADING_STATUS)) { Animation rotate_infinite = AnimationUtils.loadAnimation(mActivity, R.anim.rotate_infinite); rotate_infinite.setFillAfter(true); fabPlayButton.startAnimation(rotate_infinite); } } @Override public void onBindViewHolder(final CollectionAdapterViewHolder holder, final int position) { Log.v(LOG_TAG + "debugCollectionView", "start onBindViewHolder , position=" + String.valueOf(position)); // final int position --> Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later // get station from position final Station station = mStationList.get(position); if (mTwoPane && mStationIDSelected == position) { holder.getListItemLayout().setSelected(true); } else { holder.getListItemLayout().setSelected(false); } String TitleOfBox = getTitleOfBox(station, position); if (TitleOfBox.isEmpty()) { holder.getLayoutCategoryView().setVisibility(View.GONE); } else { holder.getLayoutCategoryView().setVisibility(View.VISIBLE); holder.getTxtCategoryView().setText(TitleOfBox); } // set station image File stationImageFile = station.getStationImage(mActivity); if (stationImageFile != null && stationImageFile.exists()) { holder.getStationImageView().setImageURI(stationImageFile.toURI().toString());//.setImageBitmap(stationImageSmall); } else if (station.IMAGE_PATH != null && station.IMAGE_PATH != "") { holder.getStationImageView().setImageURI(station.IMAGE_PATH);//.setImageBitmap(stationImageSmall); } // set station Small image (to help cache image only if it's not already cached - this cache is Async) File stationSmallImageFile = station.getStationSmallImage(mActivity); stationSmallImageFile = null; //currently not needed // set station name holder.getStationNameView().setText(station.TITLE); holder.getRatingBarView().setRating(station.RATING); //change favorit button visibility ImageButton favView = holder.getFavoritButtonView(); changeVisibilityOfFavButton(favView, station.IS_FAVOURITE); favView.setTag(position); //on click favorit icon change the status then favView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // handle fav click int positoinHere = Integer.valueOf(v.getTag().toString()); Station stationHere = mStationList.get(positoinHere); animateFovoritVisual(mActivity, v, stationHere); } private void animateFovoritVisual(Context context, final View vFavButton, final Station stationHere) { Animation rotate; final int positionHere = Integer.valueOf(vFavButton.getTag().toString()); final int newStatus = (stationHere.IS_FAVOURITE == 0) ? 1 : 0; rotate = AnimationUtils.loadAnimation(context, R.anim.rotate_clockwise_slow); // attach listener for animation end rotate.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { // send local broadcast Intent i = new Intent(); i.setAction(TransistorKeys.ACTION_COLLECTION_CHANGED); i.putExtra(TransistorKeys.EXTRA_COLLECTION_CHANGE, TransistorKeys.STATION_CHANGED_FAVORIT); i.putExtra(TransistorKeys.EXTRA_STATION_Position_ID, positionHere); i.putExtra(TransistorKeys.EXTRA_STATION, stationHere); i.putExtra(TransistorKeys.EXTRA_STATION_FAVORIT_VALUE, newStatus); LocalBroadcastManager.getInstance(mActivity.getApplication()).sendBroadcast(i); } @Override public void onAnimationRepeat(Animation animation) { } }); // start animation of button if (vFavButton != null) vFavButton.startAnimation(rotate); } }); //station description holder.getmStationDesciptionView().setText(station.DESCRIPTION); //shadow holder.getStationNameView().setShadowLayer(2, 1, 1, Color.BLACK); holder.getmStationDesciptionView().setShadowLayer(2, 1, 1, Color.BLACK); // set playback indicator - in phone view only FloatingActionButton fabPlayButton = holder.getFabPlayButton(); if (mPlayback && (station.getPlaybackState())) { if (SingletonProperties .getInstance().CurrentSelectedStation_Playback_Status == PlaybackStatus.LOADING) { holder.getPlaybackIndicator() .setBackgroundResource(R.drawable.ic_playback_indicator_small_loading_24dp); fabPlayButton.setImageResource(R.drawable.progress_loading); fabPlayButton.setTag(LOADING_STATUS); } else { holder.getPlaybackIndicator() .setBackgroundResource(R.drawable.ic_playback_indicator_small_started_24dp); fabPlayButton.setImageResource(R.drawable.smbl_stop); fabPlayButton.setTag(STOP_STATUS); } } else { //default fabPlayButton.setImageResource(R.drawable.smbl_play); fabPlayButton.setTag(PLAY_STATUS); } if (!mTwoPane && mPlayback && (station.getPlaybackState())) { // || getPositionOfCurrentPlayStation() == position holder.getPlaybackIndicator().setVisibility(View.VISIBLE); } else { holder.getPlaybackIndicator().setVisibility(View.GONE); } // attach three dots menu - in phone view only if (!mTwoPane) { holder.getStationMenuView().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { StationContextMenu menu = new StationContextMenu(); menu.initialize(mActivity, view, station, position); menu.show(); } }); } else { holder.getStationMenuView().setVisibility(View.GONE); } fabPlayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mStationIDSelected = position; LogHelper.v(LOG_TAG, "Selected station (ID): " + mStationIDSelected); if (!mTwoPane) { // long click in phone mode handlePlayStopClick(position); } else { // long click in phone mode handlePlayStopClick(position); // click in tablet mode handleSingleClick(position, holder); notifyDataSetChanged(); } } }); // attach click listener holder.setClickListener(new CollectionAdapterViewHolder.ClickListener() { @Override public void onClick(View view, int pos, boolean isLongClick) { mStationIDSelected = pos; saveAppState(mActivity); LogHelper.v(LOG_TAG, "Selected station (ID): " + mStationIDSelected); if (isLongClick && !mTwoPane) { // long click in phone mode handlePlayStopClick(pos); } else if (!isLongClick && !mTwoPane) { // click in phone mode handleSingleClick(pos, holder); } else { // click in tablet mode handleSingleClick(pos, holder); notifyDataSetChanged(); //todo:tarek review why this line here } } }); } private String getTitleOfBox(Station station, int position) { if (position == 0 && station.IS_FAVOURITE == 1) { //Fav and the first one return "Fav"; } else if (station.IS_FAVOURITE == 1) { //Fav but not the first one return ""; } else { //not Fav //check if it's first one in it's category if (position == 0) { //it's the first one ever , and not fav one return station.CATEGORY; } else if (position > 0 && (mStationList.get(position - 1).IS_FAVOURITE == 1 || !mStationList.get(position - 1).CATEGORY.equalsIgnoreCase(station.CATEGORY))) { //not first but first in it's category return station.CATEGORY; } else { return ""; } } } private void changeVisibilityOfFavButton(View vFavButton, int newStatus) { if (newStatus == 1) { ((ImageButton) vFavButton).setImageResource(R.drawable.ic_favorite_black_24dp); } else { ((ImageButton) vFavButton).setImageResource(R.drawable.ic_favorite_border_black_24dp); } } @Override public long getItemId(int position) { return position; } public int getItemPosition(long _ID) { int position = -1; for (int i = 0; i < mStationList.size(); i++) { if (mStationList.get(i)._ID == _ID) { position = i; break; } } return position; } @Override public int getItemCount() { return mStationList.size(); } /* Fills sorted list of station */ private void loadCollection() { // create folder if necessary if (!mFolder.exists()) { LogHelper.v(LOG_TAG, "Creating mFolder new folder: " + mFolder.toString()); mFolder.mkdir(); } //todo:remove that if not required (just backward compatibility) // create nomedia file to prevent media scanning File nomedia = new File(mFolder, ".nomedia"); if (!nomedia.exists()) { LogHelper.v(LOG_TAG, "Creating .nomedia file in folder: " + mFolder.toString()); try (FileOutputStream noMediaOutStream = new FileOutputStream(nomedia)) { noMediaOutStream.write(0); } catch (IOException e) { LogHelper.e(LOG_TAG, "Unable to write .nomedia file in folder: " + mFolder.toString()); } } //clear mStationList in case of having any record mStationList.clear(); FillListWithData(mActivity, mStationList); } public void FillListWithData(Activity mActivity, SortedList<Station> mStationList) { //Get from DB StationsDbHelper mDbHelper = new StationsDbHelper(mActivity); mDbHelper.FillListOfAllStations(mStationList, null); } /* Handles click on list item */ public void handleSingleClick(int position, CollectionAdapterViewHolder theHolder) { Station station = mStationList.get(position); if (mTwoPane) { Bundle args = new Bundle(); args.putParcelable(TransistorKeys.ARG_STATION, station); args.putInt(TransistorKeys.ARG_STATION_ID, position); args.putBoolean(TransistorKeys.ARG_TWO_PANE, mTwoPane); PlayerActivityFragment playerActivityFragment = new PlayerActivityFragment(); playerActivityFragment.setArguments(args); mActivity.getFragmentManager().beginTransaction() .replace(R.id.player_container, playerActivityFragment, TransistorKeys.PLAYER_FRAGMENT_TAG) .commit(); } else { // add ID of station to intent and start activity Intent intent = new Intent(mActivity, PlayerActivity.class); intent.setAction(TransistorKeys.ACTION_SHOW_PLAYER); intent.putExtra(TransistorKeys.EXTRA_STATION, station); intent.putExtra(TransistorKeys.EXTRA_STATION_Position_ID, position); if (theHolder != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { //for Motion (transaction) between activities View sharedView = theHolder.getStationImageView(); String transitionName = mActivity.getString(R.string.mainImageForSation); ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(mActivity, sharedView, transitionName); mActivity.startActivity(intent, transitionActivityOptions.toBundle()); } else { mActivity.startActivity(intent); } } } /* Handles long click on list item */ private void handlePlayStopClick(int position) { // get current playback state loadAppState(mActivity); if (mPlayback && mStationList.get(position).getPlaybackState()) { // stop player service using intent Intent intent = new Intent(mActivity, PlayerService.class); intent.setAction(TransistorKeys.ACTION_STOP); mActivity.startService(intent); LogHelper.v(LOG_TAG, "Stopping player service."); // remove playback flag from this station mPlayback = false; // inform user Toast.makeText(mActivity, mActivity.getString(R.string.toastmessage_long_press_playback_stopped), Toast.LENGTH_LONG).show(); } else { // start player service using intent Intent intent = new Intent(mActivity, PlayerService.class); intent.setAction(TransistorKeys.ACTION_PLAY); intent.putExtra(TransistorKeys.EXTRA_STATION, mStationList.get(position)); intent.putExtra(TransistorKeys.EXTRA_STATION_Position_ID, position); mActivity.startService(intent); LogHelper.v(LOG_TAG, "Starting player service."); // add playback flag to current station mPlayback = true; // inform user Toast.makeText(mActivity, mActivity.getString(R.string.toastmessage_long_press_playback_started), Toast.LENGTH_LONG).show(); } // vibrate 50 milliseconds Vibrator v = (Vibrator) mActivity.getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(50); // save app state saveAppState(mActivity); } private int getPositionOfCurrentPlayStation() { int position = -1; for (int i = 0; i < mStationList.size(); i++) { String currentRunningStationid = SingletonProperties.getInstance().CurrentStation_ID; if (String.valueOf(mStationList.get(i)._ID).equals(currentRunningStationid)) { position = i; break; } } return position; } /* Loads app state from preferences */ private void loadAppState(Context context) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); mTwoPane = settings.getBoolean(TransistorKeys.PREF_TWO_PANE, false); mStationIDSelected = settings.getInt(TransistorKeys.PREF_STATION_ID_SELECTED, 0); mPlayback = settings.getBoolean(TransistorKeys.PREF_PLAYBACK, false); LogHelper.v(LOG_TAG, "Loading state (" + SingletonProperties.getInstance().CurrentSelectedStation_ID + " / " + SingletonProperties.getInstance().getLastRunningStation_ID() + " / " + mPlayback + ")"); } /* Saves app state to SharedPreferences */ private void saveAppState(Context context) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = settings.edit(); editor.putInt(TransistorKeys.PREF_STATION_ID_SELECTED, mStationIDSelected); editor.apply(); LogHelper.v(LOG_TAG, "Saving state (" + SingletonProperties.getInstance().CurrentSelectedStation_ID + " / " + SingletonProperties.getInstance().getLastRunningStation_ID() + " / " + mPlayback + " / " + mStationIDSelected + ")"); } /* Setter for ID of currently selected station */ public void setStationIDSelected(int stationIDSelectedPosition, boolean playbackState, boolean startPlayback) { saveAppState(mActivity); if (mTwoPane && stationIDSelectedPosition >= 0 && stationIDSelectedPosition < mStationList.size()) { handleSingleClick(stationIDSelectedPosition, null); } if (startPlayback) { // start player service using intent Intent intent = new Intent(mActivity, PlayerService.class); intent.setAction(TransistorKeys.ACTION_PLAY); intent.putExtra(TransistorKeys.EXTRA_STATION, mStationList.get(stationIDSelectedPosition)); intent.putExtra(TransistorKeys.EXTRA_STATION_Position_ID, stationIDSelectedPosition); mActivity.startService(intent); LogHelper.v(LOG_TAG, "Starting player service."); } } /* Setter for two pane flag */ public void setTwoPane(boolean twoPane) { mTwoPane = twoPane; } /* Reloads app state */ public void refresh() { loadAppState(mActivity); } /* Finds station when given its Uri */ public Station findStation(Uri streamUri) { // traverse list of stations for (int i = 0; i < mStationList.size(); i++) { Station station = mStationList.get(i); if (station.getStreamUri().equals(streamUri)) { return station; } } // return null if nothing was found return null; } /* Getter for ID of given station */ public int getStationID(Station station) { return mStationList.indexOf(station); } /* Getter for station of given ID */ public Station getStation(int stationID) { return mStationList.get(stationID); } /* Add station to collection */ public int add(Station station) { if (station.TITLE != null && station.getStreamUri() != null) { // add station to list of stations mStationList.add(station); // return new index return mStationList.indexOf(station); } else { // notify user and log failure to add String errorTitle = mActivity.getResources().getString(R.string.dialog_error_title_fetch_write); String errorMessage = mActivity.getResources().getString(R.string.dialog_error_message_fetch_write); String errorDetails = mActivity.getResources().getString(R.string.dialog_error_details_write); DialogError dialogError = new DialogError(mActivity, errorTitle, errorMessage, errorDetails); dialogError.show(); LogHelper.e(LOG_TAG, "Unable to add station to collection: Duplicate name and/or stream URL."); return -1; } } /* change Rating station within collection */ public int updateItemAtPosition(Station station, int position) { // name of station is new if (station != null) { // update station list mStationList.updateItemAt(position, station); // return changed station return mStationList.indexOf(station); } else { // name of station is null or not new - notify user return -1; } } /* Rename station within collection */ public int rename(String newStationName, Station station, int stationIDPosition) { // get old station Station oldStation = mStationList.get(stationIDPosition); // name of station is new if (station != null && !oldStation.TITLE.equals(newStationName)) { //update DB StationsDbHelper mDbHelper = new StationsDbHelper(mActivity); mDbHelper.RenameStation(oldStation._ID, newStationName); //update station object station.TITLE = newStationName; // update station list mStationList.updateItemAt(stationIDPosition, station); // return changed station return mStationList.indexOf(station); } else { // name of station is null or not new - notify user Toast.makeText(mActivity, mActivity.getString(R.string.toastalert_rename_unsuccessful), Toast.LENGTH_LONG).show(); return -1; } } /* Change Fav Value station within collection */ public int changeFavoritValue(int newFavoritValue, Station station, int stationIDPosition) { // get old station Station oldStation = mStationList.get(stationIDPosition); // name of station is new if (station != null) { //update DB StationsDbHelper mDbHelper = new StationsDbHelper(mActivity); int result = mDbHelper.ChangeIsFavouriteOfStation(oldStation._ID, newFavoritValue); //update station object oldStation.IS_FAVOURITE = newFavoritValue; // update station list mStationList.updateItemAt(stationIDPosition, station); // return changed station return mStationList.indexOf(station); } else { // name of station is null or not new - notify user Toast.makeText(mActivity, mActivity.getString(R.string.toastalert_add_favorit_unsuccessful), Toast.LENGTH_LONG).show(); return -1; } } /* Delete station within collection */ public int delete(Station station) { boolean success = false; // get old station int stationID_Possition = mStationList.indexOf(station); //delete from db //update DB //db test StationsDbHelper mDbHelper = new StationsDbHelper(mActivity); int resulti = mDbHelper.DeleteStation(station._ID); if (resulti > 0) { success = true; } // remove station and notify user if (success) { int logInt = mStationList.indexOf(station); mStationList.removeItemAt(mStationList.indexOf(station)); Toast.makeText(mActivity, mActivity.getString(R.string.toastalert_delete_successful), Toast.LENGTH_LONG) .show(); } // delete station shortcut ShortcutHelper shortcutHelper = new ShortcutHelper(mActivity); shortcutHelper.removeShortcut(station); if (mTwoPane) { // determine ID of next station to display in two pane mode if (mStationList.size() >= stationID_Possition) { stationID_Possition--; } if (stationID_Possition >= 0) { // show next station Bundle args = new Bundle(); args.putParcelable(TransistorKeys.ARG_STATION, mStationList.get(stationID_Possition)); args.putInt(TransistorKeys.ARG_STATION_ID, stationID_Possition); args.putBoolean(TransistorKeys.ARG_TWO_PANE, mTwoPane); PlayerActivityFragment playerActivityFragment = new PlayerActivityFragment(); playerActivityFragment.setArguments(args); mActivity.getFragmentManager().beginTransaction() .replace(R.id.player_container, playerActivityFragment, TransistorKeys.PLAYER_FRAGMENT_TAG) .commit(); } } // return ID of next station return stationID_Possition; } /* Handles changes in state of playback, eg. start, stop, loading stream */ private void handlePlaybackStateChanged(Intent intent) { // load app state loadAppState(mActivity); if (intent.hasExtra(TransistorKeys.EXTRA_PLAYBACK_STATE_CHANGE)) { notifyDataSetChanged(); switch (intent.getIntExtra(TransistorKeys.EXTRA_PLAYBACK_STATE_CHANGE, 1)) { // CASE: player is preparing stream case TransistorKeys.PLAYBACK_LOADING_STATION: mPlayback = true; notifyDataSetChanged(); break; // CASE: playback has started case TransistorKeys.PLAYBACK_STARTED: notifyDataSetChanged(); break; // CASE: playback was stopped case TransistorKeys.PLAYBACK_STOPPED: mPlayback = false; notifyDataSetChanged(); break; } } } /* Initializes broadcast receivers for onCreate */ private void initializeBroadcastReceivers() { // RECEIVER: state of playback has changed mPlaybackStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.hasExtra(TransistorKeys.EXTRA_PLAYBACK_STATE_CHANGE)) { handlePlaybackStateChanged(intent); } } }; IntentFilter playbackStateChangedIntentFilter = new IntentFilter( TransistorKeys.ACTION_PLAYBACK_STATE_CHANGED); LocalBroadcastManager.getInstance(mActivity).registerReceiver(mPlaybackStateChangedReceiver, playbackStateChangedIntentFilter); } }