com.google.samples.apps.iosched.explore.SessionsAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.google.samples.apps.iosched.explore.SessionsAdapter.java

Source

/*
 * 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;
        }
    }
}