package org.edx.mobile.view.my_videos; import android.content.res.Configuration; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ListView; import com.google.inject.Inject; import org.edx.mobile.R; import org.edx.mobile.base.BaseFragment; import org.edx.mobile.core.IEdxEnvironment; import org.edx.mobile.interfaces.NetworkSubject; import org.edx.mobile.interfaces.SectionItemInterface; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.api.TranscriptModel; import org.edx.mobile.model.api.VideoResponseModel; import org.edx.mobile.model.db.DownloadEntry; import org.edx.mobile.module.analytics.ISegment; import org.edx.mobile.module.db.DataCallback; import org.edx.mobile.module.storage.DownloadCompletedEvent; import org.edx.mobile.module.storage.DownloadedVideoDeletedEvent; import org.edx.mobile.player.IPlayerEventCallback; import org.edx.mobile.player.PlayerFragment; import org.edx.mobile.services.LastAccessManager; import org.edx.mobile.task.GetRecentDownloadedVideosTask; import org.edx.mobile.util.AppConstants; import org.edx.mobile.util.CheckboxDrawableUtil; import org.edx.mobile.util.ResourceUtil; import org.edx.mobile.util.UiUtil; import org.edx.mobile.view.adapters.MyRecentVideoAdapter; import org.edx.mobile.view.dialog.DeleteVideoDialogFragment; import org.edx.mobile.view.dialog.IDialogCallback; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import de.greenrobot.event.EventBus; public class MyRecentVideosFragment extends BaseFragment implements IPlayerEventCallback { private MyRecentVideoAdapter adapter; private ListView videoListView; private PlayerFragment playerFragment; private DeleteVideoDialogFragment deleteDialogFragment; private int playingVideoIndex = -1; private DownloadEntry videoModel; private Button deleteButton; private MenuItem selectAllMenuItem; private final Logger logger = new Logger(getClass().getName()); private GetRecentDownloadedVideosTask getRecentDownloadedVideosTask; @Inject LastAccessManager lastAccessManager; @Inject protected IEdxEnvironment environment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); environment.getSegment().trackScreenView(ISegment.Screens.MY_VIDEOS_RECENT); setHasOptionsMenu(!isLandscape()); EventBus.getDefault().register(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View view = inflater.inflate(R.layout.fragment_video_list_with_player_container, null); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState != null) { restore(savedInstanceState); } getView().findViewById(R.id.container_player).setVisibility( playerFragment == null ? View.GONE : View.VISIBLE); videoListView = (ListView) getView().findViewById(R.id.list_video); if (videoListView != null) { adapter = new MyRecentVideoAdapter(getActivity(), environment) { @Override protected void onItemClick(SectionItemInterface model, int position) { showPlayer(); // initialize index for this model playingVideoIndex = position; videoModel = (DownloadEntry) model; playVideoModel(); notifyAdapter(); } @Override public void onSelectItem() { int selectedItemsCount = adapter.getSelectedVideoItemsCount(); int totalVideos = adapter.getTotalVideoItemsCount(); deleteButton.setEnabled(selectedItemsCount > 0); setSelectAllChecked(selectedItemsCount == totalVideos); } }; if (videoModel != null) { adapter.setVideoId(videoModel.videoId); } adapter.setSelectedPosition(playingVideoIndex); videoListView.setEmptyView(getView().findViewById(R.id.empty_list_view)); videoListView.setAdapter(adapter); showDeletePanel(getView()); videoListView.setOnItemClickListener(adapter); } } @Override public void onStart() { super.onStart(); showDeletePanel(getView()); setDeleteMode(false); } @Override public void onResume() { super.onResume(); addToRecentAdapter(); } @Override public void onStop() { super.onStop(); if (getRecentDownloadedVideosTask != null) { getRecentDownloadedVideosTask.cancel(true); getRecentDownloadedVideosTask = null; } hideConfirmDeleteDialog(); AppConstants.myVideosDeleteMode = false; } @Override public void onDestroy() { super.onDestroy(); if (playerFragment != null) { ((NetworkSubject) getActivity()).unregisterNetworkObserver(playerFragment); } EventBus.getDefault().unregister(this); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (playerFragment != null) { playerFragment.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { playerFragment.unlockOrientation(); } else { playerFragment.lockOrientation(); } } } @Override public void onDestroyOptionsMenu() { super.onDestroyOptionsMenu(); selectAllMenuItem = null; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (AppConstants.myVideosDeleteMode) { inflater.inflate(R.menu.video_list, menu); selectAllMenuItem = menu.findItem(R.id.delete_checkbox); setSelectAllChecked(adapter.getSelectedVideoItemsCount() == adapter.getTotalVideoItemsCount()); } } private void setSelectAllChecked(boolean isChecked) { selectAllMenuItem.setChecked(isChecked); selectAllMenuItem.setIcon(CheckboxDrawableUtil.createActionBarDrawable(getActivity(), isChecked)); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.delete_checkbox: { selectAllMenuItem.setChecked(!selectAllMenuItem.isChecked()); if (selectAllMenuItem.isChecked()) { adapter.selectAll(); } else { adapter.unselectAll(); } notifyAdapter(); deleteButton.setEnabled(selectAllMenuItem.isChecked()); return true; } default: { return super.onOptionsItemSelected(item); } } } private void setDeleteMode(boolean inDeleteMode) { AppConstants.myVideosDeleteMode = inDeleteMode; getActivity().supportInvalidateOptionsMenu(); } private void addToRecentAdapter() { if (adapter == null) { return; } if (getRecentDownloadedVideosTask != null) { getRecentDownloadedVideosTask.cancel(true); } logger.debug("MyRecentVideoAdapter reloading"); final String selectedId = adapter.getVideoId(); getRecentDownloadedVideosTask = new GetRecentDownloadedVideosTask(getActivity()) { @Override protected void onSuccess(List<SectionItemInterface> list) throws Exception { super.onSuccess(list); if (list != null) { adapter.clear(); adapter.addAll(list); logger.debug("MyRecentVideoAdapter reloaded."); } if (adapter.getCount() <= 0) { hideDeletePanel(getView()); } else { showDeletePanel(getView()); } videoListView.setOnItemClickListener(adapter); if (selectedId != null) { adapter.setVideoId(selectedId); } notifyAdapter(); // Refresh the previous and next buttons visibility on the video // player if a video is playing, based on the new data set. if (playerFragment != null) { playerFragment.setNextPreviousListeners(getNextListener(), getPreviousListener()); } } }; getRecentDownloadedVideosTask.execute(); } private void playVideoModel() { if (playerFragment == null) { return; } if (playerFragment.isPlaying() && videoModel.getVideoId().equals( playerFragment.getPlayingVideo().getVideoId())) { logger.debug("this video is already being played, skipping play event"); return; } VideoResponseModel vrm; try { vrm = environment.getServiceManager().getVideoById(videoModel.eid, videoModel.videoId); } catch (Exception e) { logger.error(e); return; } lastAccessManager.setLastAccessed(videoModel.eid, vrm.getSection().getId()); // reload this model environment.getStorage().reloadDownloadEntry(videoModel); logger.debug("Resumed= " + playerFragment.isResumed()); TranscriptModel transcript = null; try { transcript = environment.getServiceManager().getTranscriptsOfVideo(videoModel.eid, videoModel.videoId); } catch (Exception e) { logger.error(e); } String filepath = null; // check if file available on local if (!TextUtils.isEmpty(videoModel.filepath)) { if (videoModel.isDownloaded()) { File f = new File(videoModel.filepath); if (f.exists()) { // play from local filepath = videoModel.filepath; logger.debug("Playing from local file"); } } } else { DownloadEntry de = (DownloadEntry) environment.getDatabase() .getIVideoModelByVideoUrl(videoModel.url, null); if (de != null && de.filepath != null) { File f = new File(de.filepath); if (f.exists()) { // play from local filepath = de.filepath; logger.debug("Playing from local file for " + "another Download Entry"); } } } if (filepath == null || filepath.length() <= 0) { // not available on local, so play online logger.warn("Local file path not available"); filepath = videoModel.getBestEncodingUrl(getContext()); } playerFragment.play(filepath, videoModel.lastPlayedOffset, videoModel.getTitle(), transcript, videoModel); adapter.setVideoId(this.videoModel.videoId); } private void destroyPlayer() { if (playerFragment == null) { return; } showDeletePanel(getView()); playerFragment.lockOrientation(); getChildFragmentManager().beginTransaction().remove(playerFragment).commitAllowingStateLoss(); View container = getView().findViewById(R.id.container_player); container.setVisibility(View.GONE); playerFragment = null; } private void showPlayer() { if (playerFragment != null) { return; } hideDeletePanel(getView()); View container = getView().findViewById(R.id.container_player); if (container.getVisibility() != View.VISIBLE) { container.setVisibility(View.VISIBLE); } // add and display player fragment playerFragment = new PlayerFragment(); // set callback for player events playerFragment.setCallback(this); playerFragment.setNextPreviousListeners(getNextListener(), getPreviousListener()); FragmentManager childManager = getChildFragmentManager(); childManager.beginTransaction().add(R.id.container_player, playerFragment).commit(); // the fragment needs to be added immediately in order to be playable childManager.executePendingTransactions(); ((NetworkSubject) getActivity()).registerNetworkObserver(playerFragment); playerFragment.unlockOrientation(); } private void hideDeletePanel(View view) { // hide delete button panel at bottom view.findViewById(R.id.delete_button_panel).setVisibility(View.GONE); // hide checkbox in action bar setDeleteMode(false); // hide checkboxes in list notifyAdapter(); } private void showDeletePanel(View view) { if (playerFragment != null) { return; } LinearLayout deletePanel = (LinearLayout) view .findViewById(R.id.delete_button_panel); deletePanel.setVisibility(View.VISIBLE); deleteButton = (Button) view .findViewById(R.id.delete_btn); final Button editButton = (Button) view .findViewById(R.id.edit_btn); editButton.setVisibility(View.VISIBLE); final Button cancelButton = (Button) view .findViewById(R.id.cancel_btn); if (AppConstants.myVideosDeleteMode) { deleteButton.setVisibility(View.VISIBLE); cancelButton.setVisibility(View.VISIBLE); editButton.setVisibility(View.GONE); } else { deleteButton.setVisibility(View.GONE); cancelButton.setVisibility(View.GONE); editButton.setVisibility(View.VISIBLE); } deleteButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ArrayList<SectionItemInterface> list = adapter .getSelectedItems(); if (list != null && list.size() > 0) { showConfirmDeleteDialog(list.size()); } } }); cancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { editButton.setVisibility(View.VISIBLE); videoListView.setOnItemClickListener(adapter); setDeleteMode(false); adapter.unselectAll(); notifyAdapter(); deleteButton.setVisibility(View.GONE); cancelButton.setVisibility(View.GONE); } }); editButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { editButton.setVisibility(View.GONE); setDeleteMode(true); notifyAdapter(); videoListView.setOnItemClickListener(null); deleteButton.setEnabled(false); deleteButton.setVisibility(View.VISIBLE); cancelButton.setVisibility(View.VISIBLE); } }); } protected void showConfirmDeleteDialog(int itemCount) { Map<String, String> dialogMap = new HashMap<>(); dialogMap.put("title", getString(R.string.delete_dialog_title_help)); dialogMap.put("message_1", getResources().getQuantityString(R.plurals.delete_video_dialog_msg, itemCount)); dialogMap.put("yes_button", getString(R.string.label_delete)); dialogMap.put("no_button", getString(R.string.label_cancel)); deleteDialogFragment = DeleteVideoDialogFragment.newInstance(dialogMap, new IDialogCallback() { @Override public void onPositiveClicked() { onConfirmDelete(); deleteDialogFragment.dismiss(); } @Override public void onNegativeClicked() { deleteDialogFragment.dismiss(); } }); deleteDialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); deleteDialogFragment.show(getFragmentManager(), "dialog"); deleteDialogFragment.setCancelable(false); } protected void hideConfirmDeleteDialog() { if (deleteDialogFragment != null) { deleteDialogFragment.dismissAllowingStateLoss(); } } //Deleting Downloaded videos on getting confirmation private void onConfirmDelete() { int deletedVideoCount = 0; ArrayList<SectionItemInterface> list = adapter.getSelectedItems(); if (list != null) { for (SectionItemInterface section : list) { if (section.isDownload()) { // TODO The removeDownload() triggers a callback upon video deletion. // Would be better if removeDownload() could take a list of videos to delete. DownloadEntry de = (DownloadEntry) section; environment.getStorage().removeDownload(de); deletedVideoCount++; // Although the adapter is refreshed below, we update the adapter here to // prevent a user from being able to click a deleted video while the adapter is // refreshing. adapter.remove(section); } } } // Although the videos are removed from the adapter above, the section the videos are in // is not available so we refresh the adapter here. addToRecentAdapter(); notifyAdapter(); videoListView.setOnItemClickListener(adapter); setDeleteMode(false); if (deletedVideoCount > 0) { UiUtil.showMessage(getView(), ResourceUtil.getFormattedStringForQuantity(getResources(), R.plurals.deleted_video, "video_count", deletedVideoCount).toString()); } getView().findViewById(R.id.delete_btn).setVisibility(View.GONE); getView().findViewById(R.id.edit_btn).setVisibility(View.VISIBLE); getView().findViewById(R.id.cancel_btn).setVisibility(View.GONE); } @Override public void onError() { } @Override public void onPlaybackStarted() { environment.getStorage().markVideoPlaying(videoModel, watchedStateCallback); notifyAdapter(); } @Override public void saveCurrentPlaybackPosition(int offset) { DownloadEntry v = videoModel; if (v != null) { // mark this as partially watches, as playing has started environment.getDatabase().updateVideoLastPlayedOffset(v.videoId, offset, setCurrentPositionCallback); } notifyAdapter(); } @Override public void onPlaybackComplete() { DownloadEntry v = videoModel; if (v != null) { if (v.watched == DownloadEntry.WatchedState.PARTIALLY_WATCHED) { videoModel.watched = DownloadEntry.WatchedState.WATCHED; // mark this as partially watches, as playing has started environment.getDatabase().updateVideoWatchedState(v.videoId, DownloadEntry.WatchedState.WATCHED, watchedStateCallback); } } notifyAdapter(); } private void notifyAdapter() { if (adapter == null) { return; } adapter.setSelectedPosition(playingVideoIndex); adapter.notifyDataSetChanged(); } /** * @return true if current orientation is LANDSCAPE, false otherwise. */ protected boolean isLandscape() { return (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); } @Override public void onSaveInstanceState(Bundle outState) { logger.debug("In onSaveInstance State"); outState.putInt("playingVideoIndex", playingVideoIndex); outState.putSerializable("model", videoModel); super.onSaveInstanceState(outState); } /** * Container tab will call this method to restore the saved data */ protected void restore(Bundle savedInstanceState) { playingVideoIndex = savedInstanceState.getInt("playingVideoIndex", -1); videoModel = (DownloadEntry) savedInstanceState.getSerializable("model"); playerFragment = (PlayerFragment) getChildFragmentManager() .findFragmentById(R.id.container_player); if (playerFragment != null) { playerFragment.setCallback(this); ((NetworkSubject) getActivity()).registerNetworkObserver(playerFragment); } } //Play the nextVideo if user selects next from Dialog private void playNext() { playingVideoIndex++; // check next playable video entry while (playingVideoIndex < adapter.getCount()) { SectionItemInterface i = adapter.getItem(playingVideoIndex); if (i != null && i instanceof DownloadEntry) { videoModel = (DownloadEntry) i; adapter.setSelectedPosition(playingVideoIndex); adapter.setVideoId(videoModel.videoId); playVideoModel(); break; } // try next playingVideoIndex++; } } //Check if next video is available in the Video List private boolean hasNextVideo(int index) { if (index == -1) { index = playingVideoIndex; } for (int i = index + 1; i < adapter.getCount(); i++) { SectionItemInterface d = adapter.getItem(i); if (d != null && d instanceof DownloadEntry) { return true; } } return false; } private void playPrevious() { playingVideoIndex--; // check next playable video entry while (playingVideoIndex >= 0) { SectionItemInterface i = adapter.getItem(playingVideoIndex); if (i != null && i instanceof DownloadEntry) { videoModel = (DownloadEntry) i; adapter.setSelectedPosition(playingVideoIndex); adapter.setVideoId(videoModel.videoId); playVideoModel(); break; } // try next playingVideoIndex--; } } private boolean hasPreviousVideo(int playingIndex) { for (int i = playingIndex - 1; i >= 0; i--) { SectionItemInterface d = adapter.getItem(i); if (d != null && d instanceof DownloadEntry) { return true; } } return false; } private View.OnClickListener getNextListener() { if (hasNextVideo(playingVideoIndex)) { return new NextClickListener(); } return null; } private View.OnClickListener getPreviousListener() { if (hasPreviousVideo(playingVideoIndex)) { return new PreviousClickListener(); } return null; } private class NextClickListener implements OnClickListener { @Override public void onClick(View v) { playNext(); } } private class PreviousClickListener implements OnClickListener { @Override public void onClick(View v) { playPrevious(); } } private DataCallback<Integer> watchedStateCallback = new DataCallback<Integer>() { @Override public void onResult(Integer result) { logger.debug("Watched State Updated"); } @Override public void onFail(Exception ex) { logger.error(ex); } }; private DataCallback<Integer> setCurrentPositionCallback = new DataCallback<Integer>() { @Override public void onResult(Integer result) { logger.debug("Current Playback Position Updated"); } @Override public void onFail(Exception ex) { logger.error(ex); } }; public void onEventMainThread(DownloadCompletedEvent e) { addToRecentAdapter(); } public void onEventMainThread(DownloadedVideoDeletedEvent e) { destroyPlayer(); } }