package be.digitalia.fosdem.fragments; import android.content.Context; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.TextViewCompat; import android.support.v7.widget.AppCompatDrawableManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.TextView; import java.text.DateFormat; import be.digitalia.fosdem.R; import be.digitalia.fosdem.db.DatabaseManager; import be.digitalia.fosdem.loaders.TrackScheduleLoader; import be.digitalia.fosdem.model.Day; import be.digitalia.fosdem.model.Event; import be.digitalia.fosdem.model.Track; import be.digitalia.fosdem.utils.DateUtils; public class TrackScheduleListFragment extends SmoothListFragment implements Handler.Callback, LoaderCallbacks<Cursor> { /** * Interface implemented by container activities */ public interface Callbacks { void onEventSelected(int position, Event event); } private static final int EVENTS_LOADER_ID = 1; private static final int REFRESH_TIME_WHAT = 1; private static final long REFRESH_TIME_INTERVAL = 60 * 1000L; // 1min private static final String ARG_DAY = "day"; private static final String ARG_TRACK = "track"; private static final String ARG_FROM_EVENT_ID = "from_event_id"; private Day day; private Handler handler; private TrackScheduleAdapter adapter; private Callbacks listener; private boolean selectionEnabled = false; private boolean isListAlreadyShown = false; public static TrackScheduleListFragment newInstance(Day day, Track track) { TrackScheduleListFragment f = new TrackScheduleListFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DAY, day); args.putParcelable(ARG_TRACK, track); f.setArguments(args); return f; } public static TrackScheduleListFragment newInstance(Day day, Track track, long fromEventId) { TrackScheduleListFragment f = new TrackScheduleListFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DAY, day); args.putParcelable(ARG_TRACK, track); args.putLong(ARG_FROM_EVENT_ID, fromEventId); f.setArguments(args); return f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); day = getArguments().getParcelable(ARG_DAY); handler = new Handler(this); adapter = new TrackScheduleAdapter(getActivity()); setListAdapter(adapter); if (savedInstanceState != null) { isListAlreadyShown = savedInstanceState.getBoolean("isListAlreadyShown"); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("isListAlreadyShown", isListAlreadyShown); } @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof Callbacks) { listener = (Callbacks) context; } } @Override public void onDetach() { super.onDetach(); listener = null; } private void notifyEventSelected(int position) { if (listener != null) { listener.onEventSelected(position, (position == ListView.INVALID_POSITION) ? null : adapter.getItem(position)); } } public void setSelectionEnabled(boolean selectionEnabled) { this.selectionEnabled = selectionEnabled; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getListView().setChoiceMode(selectionEnabled ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE); setEmptyText(getString(R.string.no_data)); setListShown(false); getLoaderManager().initLoader(EVENTS_LOADER_ID, null, this); } @Override public void onStart() { super.onStart(); // Setup display auto-refresh during the track's day long now = System.currentTimeMillis(); long dayStart = day.getDate().getTime(); if (now < dayStart) { // Before track day, schedule refresh in the future handler.sendEmptyMessageDelayed(REFRESH_TIME_WHAT, dayStart - now); } else if (now < dayStart + android.text.format.DateUtils.DAY_IN_MILLIS) { // During track day, start refresh immediately adapter.setCurrentTime(now); handler.sendEmptyMessageDelayed(REFRESH_TIME_WHAT, REFRESH_TIME_INTERVAL); } else { // After track day, disable refresh adapter.setCurrentTime(-1L); } } @Override public void onStop() { handler.removeMessages(REFRESH_TIME_WHAT); super.onStop(); } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case REFRESH_TIME_WHAT: adapter.setCurrentTime(System.currentTimeMillis()); handler.sendEmptyMessageDelayed(REFRESH_TIME_WHAT, REFRESH_TIME_INTERVAL); return true; } return false; } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Track track = getArguments().getParcelable(ARG_TRACK); return new TrackScheduleLoader(getActivity(), day, track); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (data != null) { adapter.swapCursor(data); if (selectionEnabled) { final int count = adapter.getCount(); int checkedPosition = getListView().getCheckedItemPosition(); if ((checkedPosition == ListView.INVALID_POSITION) || (checkedPosition >= count)) { // There is no current valid selection, use the default one checkedPosition = getDefaultPosition(); if (checkedPosition != ListView.INVALID_POSITION) { getListView().setItemChecked(checkedPosition, true); } } // Ensure the current selection is visible if (checkedPosition != ListView.INVALID_POSITION) { getListView().setSelection(checkedPosition); } // Notify the parent of the current selection to synchronize its state notifyEventSelected(checkedPosition); } else if (!isListAlreadyShown) { int position = getDefaultPosition(); if (position != ListView.INVALID_POSITION) { getListView().setSelection(position); } } isListAlreadyShown = true; } setListShown(true); } /** * @return The default position in the list, or -1 if the list is empty */ private int getDefaultPosition() { final int count = adapter.getCount(); if (count == 0) { return ListView.INVALID_POSITION; } long fromEventId = getArguments().getLong(ARG_FROM_EVENT_ID, -1L); if (fromEventId != -1L) { // Look for the source event in the list and return its position for (int i = 0; i < count; ++i) { if (adapter.getItemId(i) == fromEventId) { return i; } } } return 0; } @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); } @Override public void onListItemClick(ListView l, View v, int position, long id) { notifyEventSelected(position); } private static class TrackScheduleAdapter extends CursorAdapter { private final LayoutInflater inflater; private final DateFormat timeDateFormat; private final int timeBackgroundColor; private final int timeForegroundColor; private final int timeRunningBackgroundColor; private final int timeRunningForegroundColor; private long currentTime = -1L; public TrackScheduleAdapter(Context context) { super(context, null, 0); inflater = LayoutInflater.from(context); timeDateFormat = DateUtils.getTimeDateFormat(context); timeBackgroundColor = ContextCompat.getColor(context, R.color.schedule_time_background); timeRunningBackgroundColor = ContextCompat.getColor(context, R.color.schedule_time_running_background); TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.PrimaryTextColors); timeForegroundColor = a.getColor(R.styleable.PrimaryTextColors_android_textColorPrimary, 0); timeRunningForegroundColor = a.getColor(R.styleable.PrimaryTextColors_android_textColorPrimaryInverse, 0); a.recycle(); } public void setCurrentTime(long time) { if (currentTime != time) { currentTime = time; notifyDataSetChanged(); } } @Override public Event getItem(int position) { return DatabaseManager.toEvent((Cursor) super.getItem(position)); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = inflater.inflate(R.layout.item_schedule_event, parent, false); ViewHolder holder = new ViewHolder(); holder.time = (TextView) view.findViewById(R.id.time); holder.title = (TextView) view.findViewById(R.id.title); holder.persons = (TextView) view.findViewById(R.id.persons); holder.room = (TextView) view.findViewById(R.id.room); view.setTag(holder); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { ViewHolder holder = (ViewHolder) view.getTag(); Event event = DatabaseManager.toEvent(cursor, holder.event); holder.event = event; String formattedTime = timeDateFormat.format(event.getStartTime()); holder.time.setText(formattedTime); if ((currentTime != -1L) && event.isRunningAtTime(currentTime)) { // Contrast colors for running event holder.time.setBackgroundColor(timeRunningBackgroundColor); holder.time.setTextColor(timeRunningForegroundColor); holder.time.setContentDescription(context.getString(R.string.in_progress_content_description, formattedTime)); } else { // Normal colors holder.time.setBackgroundColor(timeBackgroundColor); holder.time.setTextColor(timeForegroundColor); // Use text as content description holder.time.setContentDescription(null); } holder.title.setText(event.getTitle()); boolean isBookmarked = DatabaseManager.toBookmarkStatus(cursor); Drawable bookmarkDrawable = isBookmarked ? AppCompatDrawableManager.get().getDrawable(context, R.drawable.ic_bookmark_grey600_24dp) : null; TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(holder.title, null, null, bookmarkDrawable, null); holder.title.setContentDescription(isBookmarked ? context.getString(R.string.in_bookmarks_content_description, event.getTitle()) : null ); String personsSummary = event.getPersonsSummary(); holder.persons.setText(personsSummary); holder.persons.setVisibility(TextUtils.isEmpty(personsSummary) ? View.GONE : View.VISIBLE); holder.room.setText(event.getRoomName()); holder.room.setContentDescription(context.getString(R.string.room_content_description, event.getRoomName())); } static class ViewHolder { TextView time; TextView title; TextView persons; TextView room; Event event; } } }