/*
* Copyright (c) 2016 Google Inc.
*
* 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.google.samples.apps.iosched.explore;
import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.explore.data.SessionData;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.samples.apps.iosched.session.SessionDetailActivity;
import com.google.samples.apps.iosched.ui.widget.recyclerview.UpdatableAdapter;
import com.google.samples.apps.iosched.util.TimeUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.google.samples.apps.iosched.util.LogUtils.LOGE;
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
/**
* A {@link RecyclerView.Adapter} for a collection of {@link SessionData}. This adapter runs in two
* <i>modes</i>: compact and detail.
* <p/>
* Compact mode is created via the {@link #createHorizontal(Activity, List)} factory method & shows
* a smaller representation of a session (title + time). It is used by {@link ExploreIOFragment}.
* <p/>
* The detail mode is created via the {@link #createVerticalGrid(Activity, List, int)} factory
* method and adds headers for day/time blocks and shows a larger representation of a session (title
* + description). It is used by {@link ExploreSessionsFragment}.
*/
public class SessionsAdapter extends UpdatableAdapter<List<SessionData>, RecyclerView.ViewHolder> {
// Constants
private static final String TAG = makeLogTag(SessionsAdapter.class);
private static final int TYPE_SESSION = 0;
private static final int TYPE_HEADER_DAY = 1;
private static final int TYPE_HEADER_TIME = 2;
// Immutable state
private final Activity mHost;
private final LayoutInflater mInflater;
private final ColorDrawable[] mBackgroundColors;
private final boolean mCompactMode;
private final int mColumns;
// State
private List mItems;
// Private constructor, see the more meaningful static factory methods
private SessionsAdapter(@NonNull Activity activity,
@NonNull final List<SessionData> sessions,
boolean compact,
int columns) {
mHost = activity;
mInflater = LayoutInflater.from(activity);
mCompactMode = compact;
mColumns = columns;
// Load the background colors
int[] colors = mHost.getResources().getIntArray(R.array.session_tile_backgrounds);
mBackgroundColors = new ColorDrawable[colors.length];
for (int i = 0; i < colors.length; i++) {
mBackgroundColors[i] = new ColorDrawable(colors[i]);
}
mItems = processData(sessions);
}
public static SessionsAdapter createHorizontal(@NonNull Activity activity,
@NonNull final List<SessionData> sessions) {
return new SessionsAdapter(activity, sessions, true, -1);
}
public static SessionsAdapter createVerticalGrid(@NonNull Activity activity,
@NonNull final List<SessionData> sessions,
int columns) {
return new SessionsAdapter(activity, sessions, false, columns);
}
@Override
public void update(@NonNull final List<SessionData> updatedData) {
// Attempt to update data in place i.e. only if it has changed so that we don't lose scroll
// position etc when an item updates e.g. adding/removing an item from your schedule
final List newItems = processData(updatedData);
if (newItems.size() != mItems.size()) {
mItems = newItems;
notifyDataSetChanged();
return;
}
for (int i = 0; i < newItems.size(); i++) {
final Object oldItem = mItems.get(i);
final Object newItem = newItems.get(i);
if (!oldItem.equals(newItem)) {
mItems.set(i, newItem);
notifyItemChanged(i);
}
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
switch (viewType) {
case TYPE_SESSION:
return createSessionViewHolder(parent);
case TYPE_HEADER_DAY:
return createHeaderViewHolder(parent, R.layout.grid_header_minor);
case TYPE_HEADER_TIME:
return createHeaderViewHolder(parent, R.layout.grid_header_major);
default:
LOGE(TAG, "Unknown view type.");
throw new IllegalArgumentException("Unknown view type.");
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (getItemViewType(position)) {
case TYPE_SESSION:
bindSession((SessionViewHolder) holder,
(SessionData) mItems.get(position), position);
break;
case TYPE_HEADER_DAY:
bindDayHeaderHolder((HeaderViewHolder) holder, (DayHeader) mItems.get(position));
break;
case TYPE_HEADER_TIME:
bindTimeHeaderHolder((HeaderViewHolder) holder, (TimeHeader) mItems.get(position));
break;
}
}
@Override
public int getItemViewType(final int position) {
if (mCompactMode) {
return TYPE_SESSION;
}
final Object item = mItems.get(position);
if (item instanceof SessionData) {
return TYPE_SESSION;
} else if (item instanceof TimeHeader) {
return TYPE_HEADER_TIME;
} else if (item instanceof DayHeader) {
return TYPE_HEADER_DAY;
}
throw new IllegalArgumentException("Unknown view type.");
}
public int getSpanSize(int position) {
if (mCompactMode || getItemViewType(position) == TYPE_SESSION) {
return 1;
} else {
return mColumns;
}
}
/**
* Process the given list of sessions. If we are in detail mode then insert day/time headers.
*/
private List processData(final List<SessionData> sessions) {
final List data = new ArrayList(sessions.size());
// First sort sessions by start time.
// TODO Can we upstream this requirement?
Collections.sort(sessions, new Comparator<SessionData>() {
@Override
public int compare(final SessionData lhs, final SessionData rhs) {
return lhs.getStartDate().compareTo(rhs.getStartDate());
}
});
if (mCompactMode) {
data.addAll(sessions);
} else {
// loop over the sessions inserting headings at each day, hour boundary
int day = -1, time = -1;
for (SessionData session : sessions) {
if (session.getStartDate().get(Calendar.DAY_OF_YEAR) > day) {
day = session.getStartDate().get(Calendar.DAY_OF_YEAR);
data.add(new DayHeader(
TimeUtils.formatShortDate(mHost, session.getStartDate().getTime())));
time = -1;
}
if (session.getStartDate().get(Calendar.HOUR_OF_DAY) > time) {
time = session.getStartDate().get(Calendar.HOUR_OF_DAY);
data.add(new TimeHeader(
TimeUtils.formatShortTime(mHost, session.getStartDate().getTime())));
}
data.add(session);
}
}
return data;
}
@NonNull
private SessionViewHolder createSessionViewHolder(final ViewGroup parent) {
final SessionViewHolder holder = mCompactMode ? new CompactSessionViewHolder(
mInflater.inflate(R.layout.explore_io_session_list_tile, parent, false)) :
new DetailSessionViewHolder(mInflater.inflate(
R.layout.explore_sessions_session_grid_tile, parent, false));
if (mCompactMode) {
ViewCompat.setImportantForAccessibility(holder.itemView,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
ViewCompat.setImportantForAccessibility(holder.title,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
ViewCompat.setImportantForAccessibility(((CompactSessionViewHolder) holder).footer,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
return holder;
}
private HeaderViewHolder createHeaderViewHolder(ViewGroup parent,
@LayoutRes int headerLayoutRedId) {
return new HeaderViewHolder(mInflater.inflate(headerLayoutRedId, parent, false));
}
private void bindSession(SessionViewHolder holder,
final SessionData session,
final int position) {
holder.itemView.setBackgroundDrawable(
mBackgroundColors[position % mBackgroundColors.length]);
holder.itemView.setOnClickListener(mSessionClick);
holder.title.setText(session.getSessionName());
holder.inSchedule.setVisibility(session.isInSchedule() ? View.VISIBLE : View.INVISIBLE);
if (mCompactMode) {
bindCompactSession((CompactSessionViewHolder) holder, session);
} else {
bindDetailSession((DetailSessionViewHolder) holder, session);
}
}
private final View.OnClickListener mSessionClick = new View.OnClickListener() {
@Override
public void onClick(final View v) {
final ViewGroup.LayoutParams lp = v.getLayoutParams();
if (!(lp instanceof RecyclerView.LayoutParams)) {
return;
}
final int position = ((RecyclerView.LayoutParams) lp).getViewAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return;
}
final SessionData sessionData = (SessionData) mItems.get(position);
final Intent intent = new Intent(mHost, SessionDetailActivity.class);
intent.setData(ScheduleContract.Sessions.buildSessionUri(sessionData.getSessionId()));
final Bundle options;
if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) { // Lollipop
options = null;
} else {
options = ActivityOptionsCompat
.makeSceneTransitionAnimation(mHost, v,
mHost.getString(R.string.transition_session_background))
.toBundle();
}
ActivityCompat.startActivity(mHost, intent, options);
}
};
private void bindCompactSession(CompactSessionViewHolder holder, final SessionData session) {
holder.footer
.setText(TimeUtils.formatShortDateTime(mHost, session.getStartDate().getTime()));
}
private void bindDetailSession(DetailSessionViewHolder holder, final SessionData session) {
holder.description.setText(session.getDetails());
}
private void bindTimeHeaderHolder(final HeaderViewHolder holder, final TimeHeader timeHeader) {
holder.header.setText(timeHeader.time);
}
private void bindDayHeaderHolder(final HeaderViewHolder holder, final DayHeader dayHeader) {
holder.header.setText(dayHeader.day);
}
@Override
public int getItemCount() {
return mItems.size();
}
private static abstract class SessionViewHolder extends RecyclerView.ViewHolder {
final TextView title;
final ImageView inSchedule;
public SessionViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
inSchedule = (ImageView) itemView.findViewById(R.id.indicator_in_schedule);
}
}
private static class CompactSessionViewHolder extends SessionViewHolder {
final TextView footer;
public CompactSessionViewHolder(View itemView) {
super(itemView);
footer = (TextView) itemView.findViewById(R.id.footer);
}
}
private static class DetailSessionViewHolder extends SessionViewHolder {
final TextView description;
public DetailSessionViewHolder(View itemView) {
super(itemView);
description = (TextView) itemView.findViewById(R.id.description);
}
}
private static class HeaderViewHolder extends RecyclerView.ViewHolder {
final TextView header;
public HeaderViewHolder(View itemView) {
super(itemView);
header = (TextView) itemView;
}
}
private static class DayHeader {
private final String day;
public DayHeader(String day) {
this.day = day;
}
}
private static class TimeHeader {
private String time;
public TimeHeader(String time) {
this.time = time;
}
}
}