/*
* Copyright 2015 Google Inc. All rights reserved.
*
* 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.myschedule;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;
import com.google.samples.apps.iosched.Config;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.archframework.UpdatableView.UserActionListener;
import com.google.samples.apps.iosched.feedback.SessionFeedbackActivity;
import com.google.samples.apps.iosched.model.ScheduleItem;
import com.google.samples.apps.iosched.myschedule.MyScheduleModel.MyScheduleUserActionEnum;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.samples.apps.iosched.util.ImageLoader;
import com.google.samples.apps.iosched.util.LUtils;
import com.google.samples.apps.iosched.util.TimeUtils;
import com.google.samples.apps.iosched.util.UIUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
import static com.google.samples.apps.iosched.util.LogUtils.LOGE;
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
/**
* Adapter that produces views to render (one day of) the "My Schedule" screen.
*/
public class MyScheduleDayAdapter implements ListAdapter, AbsListView.RecyclerListener {
private static final String TAG = makeLogTag("MyScheduleDayAdapter");
private final Context mContext;
private final LUtils mLUtils;
// list of items served by this adapter
ArrayList<ScheduleItem> mItems = new ArrayList<>();
// observers to notify about changes in the data
ArrayList<DataSetObserver> mObservers = new ArrayList<>();
ImageLoader mImageLoader;
private final int mHourColorDefault;
private final int mHourColorPast;
private final int mTitleColorDefault;
private final int mTitleColorPast;
private final int mIconColorDefault;
private final int mIconColorPast;
private final int mColorConflict;
private final int mColorBackgroundDefault;
private final int mColorBackgroundPast;
private final int mListSpacing;
private final int mSelectableItemBackground;
private final boolean mIsRtl;
private UserActionListener<MyScheduleUserActionEnum> mListener;
public MyScheduleDayAdapter(Context context, LUtils lUtils,
UserActionListener<MyScheduleUserActionEnum> listener) {
mContext = context;
mLUtils = lUtils;
mListener = listener;
Resources resources = context.getResources();
mHourColorDefault = resources.getColor(R.color.my_schedule_hour_header_default);
mHourColorPast = resources.getColor(R.color.my_schedule_hour_header_finished);
mTitleColorDefault = resources.getColor(R.color.my_schedule_session_title_default);
mTitleColorPast = resources.getColor(R.color.my_schedule_session_title_finished);
mIconColorDefault = resources.getColor(R.color.my_schedule_icon_default);
mIconColorPast = resources.getColor(R.color.my_schedule_icon_finished);
mColorConflict = resources.getColor(R.color.my_schedule_conflict);
mColorBackgroundDefault = resources.getColor(android.R.color.white);
mColorBackgroundPast = resources.getColor(R.color.my_schedule_past_background);
mListSpacing = resources.getDimensionPixelOffset(R.dimen.element_spacing_normal);
TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.selectableItemBackground});
mSelectableItemBackground = a.getResourceId(0, 0);
a.recycle();
mIsRtl = UIUtils.isRtl(context);
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (mObservers.contains(observer)) {
mObservers.remove(observer);
}
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return position >= 0 && position < mItems.size() ? mItems.get(position) : null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return false;
}
private String formatDescription(ScheduleItem item) {
StringBuilder description = new StringBuilder();
description.append(TimeUtils.formatShortTime(mContext, new Date(item.startTime)));
if (!Config.Tags.SPECIAL_KEYNOTE.equals(item.mainTag)) {
description.append(" - ");
description.append(TimeUtils.formatShortTime(mContext, new Date(item.endTime)));
}
if (!TextUtils.isEmpty(item.room)) {
description.append(" / ");
description.append(item.room);
}
return description.toString();
}
private View.OnClickListener mUriOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Object tag = v.getTag(R.id.myschedule_uri_tagkey);
if (tag != null && tag instanceof Uri) {
Uri uri = (Uri) tag;
Bundle bundle = new Bundle();
bundle.putString(MyScheduleModel.SESSION_URL_KEY, uri.toString());
mListener.onUserAction(MyScheduleUserActionEnum.SESSION_SLOT, bundle);
mContext.startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
}
};
private void setUriClickable(View view, Uri uri) {
view.setTag(R.id.myschedule_uri_tagkey, uri);
view.setOnClickListener(mUriOnClickListener);
view.setBackgroundResource(mSelectableItemBackground);
}
private static void clearClickable(View view) {
view.setOnClickListener(null);
view.setBackgroundResource(0);
view.setClickable(false);
}
/**
* Enforces right-alignment to all the TextViews in the {@code holder}. This is not necessary if
* all the data is localized in the targeted RTL language, but as we will not be able to
* localize the conference data, we hack it.
*
* @param holder The {@link ViewHolder} of the list item.
*/
@SuppressLint("RtlHardcoded")
private void adjustForRtl(ViewHolder holder) {
if (mIsRtl) {
holder.startTime.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
holder.title.setGravity(Gravity.RIGHT);
holder.description.setGravity(Gravity.RIGHT);
holder.browse.setGravity(Gravity.RIGHT);
android.util.Log.d(TAG, "Gravity right");
}
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(mContext);
}
ViewHolder holder;
// Create a new view if it is not ready yet.
if (view == null) {
view = LayoutInflater.from(mContext).inflate(R.layout.my_schedule_item, parent, false);
holder = new ViewHolder();
holder.startTime = (TextView) view.findViewById(R.id.start_time);
holder.more = (TextView) view.findViewById(R.id.more);
holder.icon = (ImageView) view.findViewById(R.id.icon);
holder.title = (TextView) view.findViewById(R.id.slot_title);
holder.description = (TextView) view.findViewById(R.id.slot_description);
holder.browse = (TextView) view.findViewById(R.id.browse_sessions);
holder.feedback = (Button) view.findViewById(R.id.give_feedback_button);
holder.separator = view.findViewById(R.id.separator);
holder.touchArea = view.findViewById(R.id.touch_area);
holder.live = view.findViewById(R.id.live_now_badge);
view.setTag(holder);
// Typeface
mLUtils.setMediumTypeface(holder.startTime);
mLUtils.setMediumTypeface(holder.browse);
mLUtils.setMediumTypeface(holder.title);
adjustForRtl(holder);
} else {
holder = (ViewHolder) view.getTag();
// Clear event listeners
clearClickable(view);
clearClickable(holder.startTime);
clearClickable(holder.touchArea);
//Make sure it doesn't retain conflict coloring
holder.description.setTextColor(mHourColorDefault);
}
if (position < 0 || position >= mItems.size()) {
LOGE(TAG, "Invalid view position passed to MyScheduleDayAdapter: " + position);
return view;
}
final ScheduleItem item = mItems.get(position);
ScheduleItem nextItem = position < mItems.size() - 1 ? mItems.get(position + 1) : null;
long now = TimeUtils.getCurrentTime(view.getContext());
boolean isNowPlaying =
item.startTime <= now && now <= item.endTime && item.type == ScheduleItem.SESSION;
boolean isPastDuringConference = item.endTime <= now && now < Config.CONFERENCE_END_MILLIS;
if (isPastDuringConference) {
view.setBackgroundColor(mColorBackgroundPast);
holder.startTime.setTextColor(mHourColorPast);
holder.title.setTextColor(mTitleColorPast);
holder.description.setVisibility(View.GONE);
holder.icon.setColorFilter(mIconColorPast);
} else {
view.setBackgroundColor(mColorBackgroundDefault);
holder.startTime.setTextColor(mHourColorDefault);
holder.title.setTextColor(mTitleColorDefault);
holder.description.setVisibility(View.VISIBLE);
holder.icon.setColorFilter(mIconColorDefault);
}
holder.startTime.setText(TimeUtils.formatShortTime(mContext, new Date(item.startTime)));
// show or hide the "LIVE NOW" badge
holder.live.setVisibility(0 != (item.flags & ScheduleItem.FLAG_HAS_LIVESTREAM)
&& isNowPlaying ? View.VISIBLE : View.GONE);
holder.touchArea.setTag(R.id.myschedule_uri_tagkey, null);
if (item.type == ScheduleItem.FREE) {
holder.startTime.setVisibility(View.VISIBLE);
holder.more.setVisibility(View.GONE);
holder.icon.setImageResource(R.drawable.ic_browse);
holder.feedback.setVisibility(View.GONE);
holder.title.setVisibility(View.GONE);
holder.browse.setVisibility(View.VISIBLE);
setUriClickable(view, ScheduleContract.Sessions.buildUnscheduledSessionsInInterval(
item.startTime, item.endTime));
holder.description.setVisibility(View.GONE);
} else if (item.type == ScheduleItem.BREAK) {
holder.startTime.setVisibility(View.VISIBLE);
holder.more.setVisibility(View.GONE);
holder.feedback.setVisibility(View.GONE);
holder.title.setVisibility(View.VISIBLE);
holder.title.setText(item.title);
holder.icon.setImageResource(UIUtils.getBreakIcon(item.title));
holder.browse.setVisibility(View.GONE);
holder.description.setText(formatDescription(item));
} else if (item.type == ScheduleItem.SESSION) {
if (holder.feedback != null) {
boolean showFeedbackButton = !item.hasGivenFeedback;
// Can't use isPastDuringConference because we want to show feedback after the
// conference too.
if (showFeedbackButton) {
if (item.endTime > now) {
// Session hasn't finished yet, don't show button.
showFeedbackButton = false;
}
}
holder.feedback.setVisibility(showFeedbackButton ? View.VISIBLE : View.GONE);
holder.feedback.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString(MyScheduleModel.SESSION_ID_KEY, item.sessionId);
bundle.putString(MyScheduleModel.SESSION_TITLE_KEY, item.title);
mListener.onUserAction(MyScheduleUserActionEnum.FEEDBACK, bundle);
Intent feedbackIntent = new Intent(Intent.ACTION_VIEW,
ScheduleContract.Sessions.buildSessionUri(item.sessionId),
mContext, SessionFeedbackActivity.class);
mContext.startActivity(feedbackIntent);
}
});
}
holder.title.setVisibility(View.VISIBLE);
holder.title.setText(item.title);
holder.more.setVisibility(item.isKeynote() ? View.GONE : View.VISIBLE);
holder.browse.setVisibility(View.GONE);
holder.icon.setImageResource(UIUtils.getSessionIcon(item.sessionType));
final Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(item.sessionId);
if (0 != (item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_PREVIOUS)) {
holder.startTime.setVisibility(View.GONE);
holder.description.setTextColor(mColorConflict);
setUriClickable(holder.touchArea, sessionUri);
} else {
holder.startTime.setVisibility(View.VISIBLE);
setUriClickable(holder.startTime, ScheduleContract.Sessions
.buildUnscheduledSessionsInInterval(item.startTime, item.endTime));
// Padding fix needed for KitKat (padding gets removed by setting the background)
holder.startTime.setPadding(
(int) mContext.getResources().getDimension(R.dimen.keyline_2), 0,
(int) mContext.getResources().getDimension(R.dimen.keyline_2), 0);
setUriClickable(holder.touchArea, sessionUri);
if (0 != (item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_NEXT)) {
holder.description.setTextColor(mColorConflict);
}
}
holder.description.setText(formatDescription(item));
} else {
LOGE(TAG, "Invalid item type in MyScheduleDayAdapter: " + item.type);
}
holder.separator.setVisibility(nextItem == null ||
0 != (item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_NEXT) ? View.GONE :
View.VISIBLE);
if (position == 0) { // First item
view.setPadding(0, mListSpacing, 0, 0);
} else if (nextItem == null) { // Last item
view.setPadding(0, 0, 0, mListSpacing);
} else {
view.setPadding(0, 0, 0, 0);
}
return view;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean isEmpty() {
return mItems.isEmpty();
}
public void clear() {
updateItems(null);
}
private void notifyObservers() {
for (DataSetObserver observer : mObservers) {
observer.onChanged();
}
}
public void forceUpdate() {
notifyObservers();
}
public void updateItems(List<ScheduleItem> items) {
mItems.clear();
if (items != null) {
for (ScheduleItem item : items) {
LOGD(TAG, "Adding schedule item: " + item + " start=" + new Date(item.startTime));
mItems.add((ScheduleItem) item.clone());
}
}
notifyObservers();
}
@Override
public void onMovedToScrapHeap(View view) {
}
private static class ViewHolder {
public TextView startTime;
public TextView more;
public ImageView icon;
public TextView title;
public TextView description;
public Button feedback;
public TextView browse;
public View live;
public View separator;
public View touchArea;
}
}