package org.edx.mobile.view.adapters; import android.content.Context; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.TextView; import com.joanzapata.iconify.fonts.FontAwesomeIcons; import com.joanzapata.iconify.internal.Animation; import com.joanzapata.iconify.widget.IconImageView; import org.edx.mobile.R; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.course.BlockPath; import org.edx.mobile.model.course.BlockType; import org.edx.mobile.model.course.CourseComponent; import org.edx.mobile.model.course.DiscussionBlockModel; import org.edx.mobile.model.course.HasDownloadEntry; import org.edx.mobile.model.course.IBlock; import org.edx.mobile.model.course.VideoBlockModel; import org.edx.mobile.model.db.DownloadEntry; import org.edx.mobile.module.db.DataCallback; import org.edx.mobile.module.db.IDatabase; import org.edx.mobile.module.storage.IStorage; import org.edx.mobile.util.Config; import java.util.ArrayList; import java.util.List; /** * Used for pinned behavior. */ public class CourseOutlineAdapter extends BaseAdapter { private final Logger logger = new Logger(getClass().getName()); public interface DownloadListener { void download(List<? extends HasDownloadEntry> models); void download(DownloadEntry videoData); void viewDownloadsStatus(); } private CourseComponent rootComponent; private LayoutInflater mInflater; private List<SectionRow> mData; private IDatabase dbStore; private IStorage storage; private DownloadListener mDownloadListener; private Config config; public CourseOutlineAdapter(Context context, Config config, IDatabase dbStore, IStorage storage, DownloadListener listener) { this.config = config; this.dbStore = dbStore; this.storage = storage; this.mDownloadListener = listener; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mData = new ArrayList(); } @Override public int getItemViewType(int position) { return getItem(position).type; } @Override public int getViewTypeCount() { return 2; } @Override public int getCount() { return mData.size(); } @Override public SectionRow getItem(int position) { if (position < 0 || position >= mData.size()) return null; return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public boolean isEnabled(int position) { return getItemViewType(position) == SectionRow.ITEM; } @Override public boolean areAllItemsEnabled() { return false; } @Override public final View getView(int position, View convertView, ViewGroup parent) { int type = getItemViewType(position); // FIXME: Re-enable row recycling in favor of better DB communication [MA-1640] //if (convertView == null) { switch (type) { case SectionRow.ITEM: { convertView = mInflater.inflate(R.layout.row_course_outline_list, parent, false); // apply a tag to this list row ViewHolder tag = getTag(convertView); convertView.setTag(tag); break; } case SectionRow.SECTION: { convertView = mInflater.inflate(R.layout.row_section_header, parent, false); break; } default: { throw new IllegalArgumentException(String.valueOf(type)); } } //} switch (type) { case SectionRow.ITEM: { return getRowView(position, convertView); } case SectionRow.SECTION: { return getHeaderView(position, convertView); } default: { throw new IllegalArgumentException(String.valueOf(type)); } } } /** * component can be null. * * @IComponent component should be ICourse */ public void setData(CourseComponent component) { if (component != null && !component.isContainer()) return;// this.rootComponent = component; mData.clear(); if (rootComponent != null) { List<IBlock> children = rootComponent.getChildren(); for (IBlock block : children) { CourseComponent comp = (CourseComponent) block; if (comp.isContainer()) { SectionRow header = new SectionRow(SectionRow.SECTION, comp); mData.add(header); for (IBlock childBlock : comp.getChildren()) { CourseComponent child = (CourseComponent) childBlock; SectionRow row = new SectionRow(SectionRow.ITEM, false, child); mData.add(row); } } else { SectionRow row = new SectionRow(SectionRow.ITEM, true, comp); mData.add(row); } } } notifyDataSetChanged(); } public void reloadData() { if (this.rootComponent != null) setData(this.rootComponent); } public View getRowView(int position, View convertView) { final SectionRow row = this.getItem(position); final SectionRow nextRow = this.getItem(position + 1); final CourseComponent component = row.component; final ViewHolder viewHolder = (ViewHolder) convertView.getTag(); if (nextRow == null) { viewHolder.halfSeparator.setVisibility(View.GONE); viewHolder.wholeSeparator.setVisibility(View.VISIBLE); } else { viewHolder.wholeSeparator.setVisibility(View.GONE); boolean isLastChildInBlock = !row.component.getParent().getId().equals(nextRow.component.getParent().getId()); if (isLastChildInBlock) { viewHolder.halfSeparator.setVisibility(View.GONE); } else { viewHolder.halfSeparator.setVisibility(View.VISIBLE); } } viewHolder.rowType.setVisibility(View.GONE); viewHolder.rowSubtitleIcon.setVisibility(View.GONE); viewHolder.rowSubtitle.setVisibility(View.GONE); viewHolder.rowSubtitlePanel.setVisibility(View.GONE); viewHolder.numOfVideoAndDownloadArea.setVisibility(View.GONE); if (component.isContainer()) { getRowViewForContainer(viewHolder, row); } else { getRowViewForLeaf(viewHolder, row); } return convertView; } private void getRowViewForLeaf(ViewHolder viewHolder, final SectionRow row) { final CourseComponent unit = row.component; viewHolder.rowType.setVisibility(View.VISIBLE); viewHolder.rowSubtitleIcon.setVisibility(View.GONE); viewHolder.rowSubtitle.setVisibility(View.GONE); viewHolder.rowSubtitlePanel.setVisibility(View.GONE); viewHolder.bulkDownload.setVisibility(View.INVISIBLE); viewHolder.rowTitle.setText(unit.getDisplayName()); if (row.component instanceof VideoBlockModel) { final DownloadEntry videoData = ((VideoBlockModel) row.component).getDownloadEntry(storage); if (null != videoData) { updateUIForVideo(viewHolder, videoData); return; } } if (config.isDiscussionsEnabled() && row.component instanceof DiscussionBlockModel) { viewHolder.rowType.setIcon(FontAwesomeIcons.fa_comments_o); checkAccessStatus(viewHolder, unit); } else if (!unit.isMultiDevice()) { // If we reach here & the type is VIDEO, it means the video is webOnly viewHolder.bulkDownload.setVisibility(View.INVISIBLE); viewHolder.rowType.setIcon(FontAwesomeIcons.fa_laptop); viewHolder.rowType.setIconColorResource(R.color.edx_brand_gray_accent); } else { viewHolder.bulkDownload.setVisibility(View.INVISIBLE); if (unit.getType() == BlockType.PROBLEM) { viewHolder.rowType.setIcon(FontAwesomeIcons.fa_list); } else { viewHolder.rowType.setIcon(FontAwesomeIcons.fa_file_o); } checkAccessStatus(viewHolder, unit); } } private void checkAccessStatus(final ViewHolder viewHolder, final CourseComponent unit) { dbStore.isUnitAccessed(new DataCallback<Boolean>(true) { @Override public void onResult(Boolean accessed) { if (accessed) { viewHolder.rowType.setIconColorResource(R.color.edx_brand_gray_accent); } else { viewHolder.rowType.setIconColorResource(R.color.edx_brand_primary_base); } } @Override public void onFail(Exception ex) { logger.error(ex); } }, unit.getId()); } private void updateUIForVideo(@NonNull final ViewHolder viewHolder, @NonNull final DownloadEntry videoData) { viewHolder.rowType.setIcon(FontAwesomeIcons.fa_film); viewHolder.numOfVideoAndDownloadArea.setVisibility(View.VISIBLE); viewHolder.bulkDownload.setVisibility(View.VISIBLE); viewHolder.rowSubtitlePanel.setVisibility(View.VISIBLE); viewHolder.rowSubtitle.setVisibility(View.VISIBLE); viewHolder.rowSubtitle.setText(videoData.getDurationReadable()); dbStore.getWatchedStateForVideoId(videoData.videoId, new DataCallback<DownloadEntry.WatchedState>(true) { @Override public void onResult(DownloadEntry.WatchedState result) { if (result != null && result == DownloadEntry.WatchedState.WATCHED) { viewHolder.rowType.setIconColorResource(R.color.edx_brand_gray_accent); } else { viewHolder.rowType.setIconColorResource(R.color.edx_brand_primary_base); } } @Override public void onFail(Exception ex) { logger.error(ex); } }); if (videoData.isVideoForWebOnly()) { viewHolder.numOfVideoAndDownloadArea.setVisibility(View.GONE); } else { viewHolder.numOfVideoAndDownloadArea.setVisibility(View.VISIBLE); dbStore.getDownloadedStateForVideoId(videoData.videoId, new DataCallback<DownloadEntry.DownloadedState>(true) { @Override public void onResult(DownloadEntry.DownloadedState state) { if (state == null || state == DownloadEntry.DownloadedState.ONLINE) { // not yet downloaded setRowStateOnDownload(viewHolder, DownloadEntry.DownloadedState.ONLINE, new View.OnClickListener() { @Override public void onClick(View v) { mDownloadListener.download(videoData); } }); } else if (state == DownloadEntry.DownloadedState.DOWNLOADING) { // may be download in progress setRowStateOnDownload(viewHolder, DownloadEntry.DownloadedState.DOWNLOADING, new View.OnClickListener() { @Override public void onClick(View v) { mDownloadListener.viewDownloadsStatus(); } }); } else if (state == DownloadEntry.DownloadedState.DOWNLOADED) { setRowStateOnDownload(viewHolder, DownloadEntry.DownloadedState.DOWNLOADED, null); } } @Override public void onFail(Exception ex) { logger.error(ex); viewHolder.bulkDownload.setVisibility(View.VISIBLE); } }); } } private void getRowViewForContainer(ViewHolder holder, final SectionRow row) { final CourseComponent component = row.component; String courseId = component.getCourseId(); BlockPath path = component.getPath(); //FIXME - we should add a new column in database - pathinfo. //then do the string match to get the record String chapterId = path.get(1) == null ? "" : path.get(1).getDisplayName(); String sequentialId = path.get(2) == null ? "" : path.get(2).getDisplayName(); holder.rowTitle.setText(component.getDisplayName()); holder.numOfVideoAndDownloadArea.setVisibility(View.VISIBLE); if (component.isGraded()) { holder.bulkDownload.setVisibility(View.INVISIBLE); holder.rowSubtitlePanel.setVisibility(View.VISIBLE); holder.rowSubtitleIcon.setVisibility(View.VISIBLE); holder.rowSubtitle.setVisibility(View.VISIBLE); holder.rowSubtitle.setText(component.getFormat()); } final int totalDownloadableVideos = component.getDownloadableVideosCount(); // support video download for video type excluding the ones only viewable on web if (totalDownloadableVideos == 0) { holder.numOfVideoAndDownloadArea.setVisibility(View.GONE); } else { holder.bulkDownload.setVisibility(View.VISIBLE); holder.noOfVideos.setVisibility(View.VISIBLE); holder.noOfVideos.setText("" + totalDownloadableVideos); Integer downloadedCount = dbStore.getDownloadedVideosCountForSection(courseId, chapterId, sequentialId, null); if (downloadedCount == totalDownloadableVideos) { holder.noOfVideos.setVisibility(View.VISIBLE); setRowStateOnDownload(holder, DownloadEntry.DownloadedState.DOWNLOADED, null); } else if (dbStore.getDownloadingVideosCountForSection(courseId, chapterId, sequentialId, null) + downloadedCount == totalDownloadableVideos) { holder.noOfVideos.setVisibility(View.GONE); setRowStateOnDownload(holder, DownloadEntry.DownloadedState.DOWNLOADING, new View.OnClickListener() { @Override public void onClick(View downloadView) { mDownloadListener.viewDownloadsStatus(); } }); } else { holder.noOfVideos.setVisibility(View.VISIBLE); setRowStateOnDownload(holder, DownloadEntry.DownloadedState.ONLINE, new View.OnClickListener() { @Override public void onClick(View downloadView) { mDownloadListener.download(component.getVideos()); } }); } } } /** * Makes various changes to the row based on a video element's download status * * @param row ViewHolder of the row view * @param state current state of video download * @param listener the listener to attach to the video download button */ private void setRowStateOnDownload(ViewHolder row, DownloadEntry.DownloadedState state , View.OnClickListener listener) { switch (state) { case DOWNLOADING: row.bulkDownload.setIcon(FontAwesomeIcons.fa_spinner); row.bulkDownload.setIconAnimation(Animation.PULSE); row.bulkDownload.setIconColorResource(R.color.edx_brand_primary_base); break; case DOWNLOADED: row.bulkDownload.setIcon(FontAwesomeIcons.fa_check); row.bulkDownload.setIconAnimation(Animation.NONE); row.bulkDownload.setIconColorResource(R.color.edx_brand_gray_accent); break; case ONLINE: row.bulkDownload.setIcon(FontAwesomeIcons.fa_arrow_down); row.bulkDownload.setIconAnimation(Animation.NONE); row.bulkDownload.setIconColorResource(R.color.edx_brand_gray_accent); break; } row.numOfVideoAndDownloadArea.setOnClickListener(listener); } public View getHeaderView(int position, View convertView) { final SectionRow row = this.getItem(position); TextView titleView = (TextView) convertView.findViewById(R.id.row_header); View separator = convertView.findViewById(R.id.row_separator); titleView.setText(row.component.getDisplayName()); if (position == 0) { separator.setVisibility(View.GONE); } else { separator.setVisibility(View.VISIBLE); } return convertView; } public ViewHolder getTag(View convertView) { ViewHolder holder = new ViewHolder(); holder.rowType = (IconImageView) convertView .findViewById(R.id.row_type); holder.rowTitle = (TextView) convertView .findViewById(R.id.row_title); holder.rowSubtitle = (TextView) convertView .findViewById(R.id.row_subtitle); holder.rowSubtitleIcon = (IconImageView) convertView .findViewById(R.id.row_subtitle_icon); holder.rowSubtitleIcon.setIconColorResource(R.color.edx_brand_gray_back); holder.noOfVideos = (TextView) convertView .findViewById(R.id.no_of_videos); holder.bulkDownload = (IconImageView) convertView .findViewById(R.id.bulk_download); holder.bulkDownload.setIconColorResource(R.color.edx_brand_gray_accent); holder.numOfVideoAndDownloadArea = (LinearLayout) convertView .findViewById(R.id.bulk_download_layout); holder.rowSubtitlePanel = convertView.findViewById(R.id.row_subtitle_panel); holder.halfSeparator = convertView.findViewById(R.id.row_half_separator); holder.wholeSeparator = convertView.findViewById(R.id.row_whole_separator); // Accessibility ViewCompat.setImportantForAccessibility(holder.rowSubtitle, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO); return holder; } public static class ViewHolder { IconImageView rowType; TextView rowTitle; TextView rowSubtitle; IconImageView rowSubtitleIcon; IconImageView bulkDownload; TextView noOfVideos; LinearLayout numOfVideoAndDownloadArea; View rowSubtitlePanel; View halfSeparator; View wholeSeparator; } public static class SectionRow { public static final int ITEM = 0; public static final int SECTION = 1; public final int type; public final boolean topComponent; public final CourseComponent component; public SectionRow(int type, CourseComponent component) { this(type, false, component); } public SectionRow(int type, boolean topComponent, CourseComponent component) { this.type = type; this.topComponent = topComponent; this.component = component; } } public int getPositionByItemId(String itemId) { int size = getCount(); for (int i = 0; i < size; i++) { if (getItem(i).component.getId().equals(itemId)) return i; } return -1; } }