com.lullabot.android.apps.iosched.ui.SessionDetailFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.lullabot.android.apps.iosched.ui.SessionDetailFragment.java

Source

/*
 * Copyright 2011 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.lullabot.android.apps.iosched.ui;

import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import com.lullabot.android.apps.iosched.R;
import com.lullabot.android.apps.iosched.provider.ScheduleContract;
import com.lullabot.android.apps.iosched.util.ActivityHelper;
import com.lullabot.android.apps.iosched.util.AnalyticsUtils;
import com.lullabot.android.apps.iosched.util.BitmapUtils;
import com.lullabot.android.apps.iosched.util.CatchNotesHelper;
import com.lullabot.android.apps.iosched.util.FractionalTouchDelegate;
import com.lullabot.android.apps.iosched.util.NotifyingAsyncQueryHandler;
import com.lullabot.android.apps.iosched.util.UIUtils;

/**
 * A fragment that shows detail information for a session, including session title, abstract,
 * time information, speaker photos and bios, etc.
 */
public class SessionDetailFragment extends Fragment
        implements NotifyingAsyncQueryHandler.AsyncQueryListener, CompoundButton.OnCheckedChangeListener {

    /**
     * Since sessions can belong tracks, the parent activity can send this extra specifying a
     * track URI that should be used for coloring the title-bar.
     */
    public static final String EXTRA_TRACK = "com.google.android.iosched.extra.TRACK";

    private static final String TAG_SUMMARY = "summary";

    private String mSessionId;
    private Uri mSessionUri;
    private Uri mTrackUri;

    private String mTitleString;
    private String mHashtag;
    private String mUrl;

    private ViewGroup mRootView;
    private TextView mTitle;
    private TextView mSubtitle;
    private CompoundButton mStarred;

    private TextView mAbstract;

    private NotifyingAsyncQueryHandler mHandler;

    private boolean mSessionCursor = false;
    private boolean mSpeakersCursor = false;
    private boolean mHasSummaryContent = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
        mSessionUri = intent.getData();
        mTrackUri = resolveTrackUri(intent);

        if (mSessionUri == null) {
            return;
        }

        mSessionId = ScheduleContract.Sessions.getSessionId(mSessionUri);

        setHasOptionsMenu(true);
    }

    @Override
    public void onResume() {
        super.onResume();

        // Start listening for time updates to adjust "now" bar. TIME_TICK is
        // triggered once per minute, which is how we move the bar over time.
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addDataScheme("package");
        getActivity().registerReceiver(mPackageChangesReceiver, filter);
    }

    @Override
    public void onPause() {
        super.onPause();
        getActivity().unregisterReceiver(mPackageChangesReceiver);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (mSessionUri == null) {
            return;
        }

        // Start background queries to load session and track details
        final Uri speakersUri = ScheduleContract.Sessions.buildSpeakersDirUri(mSessionId);

        mHandler = new NotifyingAsyncQueryHandler(getActivity().getContentResolver(), this);
        mHandler.startQuery(SessionsQuery._TOKEN, mSessionUri, SessionsQuery.PROJECTION);
        mHandler.startQuery(TracksQuery._TOKEN, mTrackUri, TracksQuery.PROJECTION);
        mHandler.startQuery(SpeakersQuery._TOKEN, speakersUri, SpeakersQuery.PROJECTION);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        mRootView = (ViewGroup) inflater.inflate(R.layout.fragment_session_detail, null);

        mTitle = (TextView) mRootView.findViewById(R.id.session_title);
        mSubtitle = (TextView) mRootView.findViewById(R.id.session_subtitle);
        mStarred = (CompoundButton) mRootView.findViewById(R.id.star_button);

        mStarred.setFocusable(true);
        mStarred.setClickable(true);

        // Larger target triggers star toggle
        final View starParent = mRootView.findViewById(R.id.header_session);
        FractionalTouchDelegate.setupDelegate(starParent, mStarred, new RectF(0.6f, 0f, 1f, 0.8f));

        mAbstract = (TextView) mRootView.findViewById(R.id.session_abstract);

        setupSummaryTab();

        return mRootView;
    }

    /**
     * Build and add "summary" tab.
     */
    private void setupSummaryTab() {
        // Summary content comes from existing layout
        //        mTabHost.addTab(mTabHost.newTabSpec(TAG_SUMMARY)
        //                .setIndicator(buildIndicator(R.string.session_summary))
        //                .setContent(R.id.tab_session_summary));
    }

    /**
     * Build a {@link View} to be used as a tab indicator, setting the requested string resource as
     * its label.
     *
     * @param textRes
     * @return View
     */
    //    private View buildIndicator(int textRes) {
    ////        final TextView indicator = (TextView) getActivity().getLayoutInflater()
    ////                .inflate(R.layout.tab_indicator,
    ////                        (ViewGroup) mRootView.findViewById(android.R.id.tabs), false);
    ////        indicator.setText(textRes);
    ////        return indicator;
    //    }

    /**
     * Derive {@link com.lullabot.android.apps.iosched.provider.ScheduleContract.Tracks#CONTENT_ITEM_TYPE}
     * {@link Uri} based on incoming {@link Intent}, using
     * {@link #EXTRA_TRACK} when set.
     * @param intent
     * @return Uri
     */
    private Uri resolveTrackUri(Intent intent) {
        final Uri trackUri = intent.getParcelableExtra(EXTRA_TRACK);
        if (trackUri != null) {
            return trackUri;
        } else {
            return ScheduleContract.Sessions.buildTracksDirUri(mSessionId);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
        if (getActivity() == null) {
            return;
        }

        if (token == SessionsQuery._TOKEN) {
            onSessionQueryComplete(cursor);
        } else if (token == TracksQuery._TOKEN) {
            onTrackQueryComplete(cursor);
        } else if (token == SpeakersQuery._TOKEN) {
            onSpeakersQueryComplete(cursor);
        } else {
            cursor.close();
        }
    }

    /**
     * Handle {@link SessionsQuery} {@link Cursor}.
     */
    private void onSessionQueryComplete(Cursor cursor) {
        try {
            mSessionCursor = true;
            if (!cursor.moveToFirst()) {
                return;
            }

            // Format time block this session occupies
            final long blockStart = cursor.getLong(SessionsQuery.BLOCK_START);
            final long blockEnd = cursor.getLong(SessionsQuery.BLOCK_END);
            final String roomName = cursor.getString(SessionsQuery.ROOM_NAME);
            final String subtitle = UIUtils.formatSessionSubtitle(blockStart, blockEnd, roomName, getActivity());

            mTitleString = cursor.getString(SessionsQuery.TITLE);
            mTitle.setText(mTitleString);
            mSubtitle.setText(subtitle);

            mUrl = cursor.getString(SessionsQuery.URL);
            if (TextUtils.isEmpty(mUrl)) {
                mUrl = "";
            }
            mHashtag = cursor.getString(SessionsQuery.HASHTAG);

            // Unregister around setting checked state to avoid triggering
            // listener since change isn't user generated.
            mStarred.setOnCheckedChangeListener(null);
            mStarred.setChecked(cursor.getInt(SessionsQuery.STARRED) != 0);
            mStarred.setOnCheckedChangeListener(this);

            final String sessionAbstract = cursor.getString(SessionsQuery.ABSTRACT);
            if (!TextUtils.isEmpty(sessionAbstract)) {
                UIUtils.setTextMaybeHtml(mAbstract, sessionAbstract);
                mAbstract.setVisibility(View.VISIBLE);
                mHasSummaryContent = true;
            } else {
                mAbstract.setVisibility(View.GONE);
            }

            // Show empty message when all data is loaded, and nothing to show
            if (mSpeakersCursor && !mHasSummaryContent) {
                mRootView.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
            }

            AnalyticsUtils.getInstance(getActivity()).trackPageView("/Sessions/" + mTitleString);

        } finally {
            cursor.close();
        }
    }

    /**
     * Handle {@link TracksQuery} {@link Cursor}.
     */
    private void onTrackQueryComplete(Cursor cursor) {
        try {
            if (!cursor.moveToFirst()) {
                return;
            }

            // Use found track to build title-bar
            ActivityHelper activityHelper = ((BaseActivity) getActivity()).getActivityHelper();
            activityHelper.setActionBarTitle(cursor.getString(TracksQuery.TRACK_NAME));
            activityHelper.setActionBarColor(cursor.getInt(TracksQuery.TRACK_COLOR));
        } finally {
            cursor.close();
        }
    }

    private void onSpeakersQueryComplete(Cursor cursor) {
        try {
            mSpeakersCursor = true;
            // TODO: remove any existing speakers from layout, since this cursor
            // might be from a data change notification.
            final ViewGroup speakersGroup = (ViewGroup) mRootView.findViewById(R.id.session_speakers_block);
            final LayoutInflater inflater = getActivity().getLayoutInflater();

            boolean hasSpeakers = false;

            while (cursor.moveToNext()) {
                final String speakerName = cursor.getString(SpeakersQuery.SPEAKER_NAME);
                if (TextUtils.isEmpty(speakerName)) {
                    continue;
                }

                final String speakerImageUrl = cursor.getString(SpeakersQuery.SPEAKER_IMAGE_URL);
                final String speakerCompany = cursor.getString(SpeakersQuery.SPEAKER_COMPANY);
                final String speakerUrl = cursor.getString(SpeakersQuery.SPEAKER_URL);
                final String speakerAbstract = cursor.getString(SpeakersQuery.SPEAKER_ABSTRACT);

                String speakerHeader = speakerName;
                if (!TextUtils.isEmpty(speakerCompany)) {
                    speakerHeader += ", " + speakerCompany;
                }

                final View speakerView = inflater.inflate(R.layout.speaker_detail, speakersGroup, false);
                final TextView speakerHeaderView = (TextView) speakerView.findViewById(R.id.speaker_header);
                final ImageView speakerImgView = (ImageView) speakerView.findViewById(R.id.speaker_image);
                final TextView speakerUrlView = (TextView) speakerView.findViewById(R.id.speaker_url);
                final TextView speakerAbstractView = (TextView) speakerView.findViewById(R.id.speaker_abstract);

                if (!TextUtils.isEmpty(speakerImageUrl)) {
                    BitmapUtils.fetchImage(getActivity(), speakerImageUrl, null, null,
                            new BitmapUtils.OnFetchCompleteListener() {
                                public void onFetchComplete(Object cookie, Bitmap result) {
                                    if (result != null) {
                                        speakerImgView.setImageBitmap(result);
                                    }
                                }
                            });
                }

                speakerHeaderView.setText(speakerHeader);
                UIUtils.setTextMaybeHtml(speakerAbstractView, speakerAbstract);

                if (!TextUtils.isEmpty(speakerUrl)) {
                    UIUtils.setTextMaybeHtml(speakerUrlView, speakerUrl);
                    speakerUrlView.setVisibility(View.VISIBLE);
                } else {
                    speakerUrlView.setVisibility(View.GONE);
                }

                speakersGroup.addView(speakerView);
                hasSpeakers = true;
                mHasSummaryContent = true;
            }

            speakersGroup.setVisibility(hasSpeakers ? View.VISIBLE : View.GONE);

            // Show empty message when all data is loaded, and nothing to show
            if (mSessionCursor && !mHasSummaryContent) {
                mRootView.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
            }
        } finally {
            if (null != cursor) {
                cursor.close();
            }
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.session_detail_menu_items, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        final String shareString;
        final Intent intent;

        switch (item.getItemId()) {
        case R.id.menu_share:
            // TODO: consider bringing in shortlink to session
            shareString = getString(R.string.share_template, mTitleString, getHashtagsString(), mUrl);
            intent = new Intent(Intent.ACTION_SEND);
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_TEXT, shareString);
            startActivity(Intent.createChooser(intent, getText(R.string.title_share)));
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Handle toggling of starred checkbox.
     */
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        final ContentValues values = new ContentValues();
        values.put(ScheduleContract.Sessions.SESSION_STARRED, isChecked ? 1 : 0);
        mHandler.startUpdate(mSessionUri, values);

        // Because change listener is set to null during initialization, these won't fire on
        // pageview.
        AnalyticsUtils.getInstance(getActivity()).trackEvent("Sandbox", isChecked ? "Starred" : "Unstarred",
                mTitleString, 0);
    }

    /*
     * Event structure:
     * Category -> "Session Details"
     * Action -> "Create Note", "View Note", etc
     * Label -> Session's Title
     * Value -> 0.
     */
    public void fireNotesEvent(int actionId) {
        AnalyticsUtils.getInstance(getActivity()).trackEvent("Session Details", getActivity().getString(actionId),
                mTitleString, 0);
    }

    /*
     * Event structure:
     * Category -> "Session Details"
     * Action -> Link Text
     * Label -> Session's Title
     * Value -> 0.
     */
    public void fireLinkEvent(int actionId) {
        AnalyticsUtils.getInstance(getActivity()).trackEvent("Link Details", getActivity().getString(actionId),
                mTitleString, 0);
    }

    private String getHashtagsString() {
        if (!TextUtils.isEmpty(mHashtag)) {
            return TagStreamFragment.CONFERENCE_HASHTAG + " " + mHashtag;
        } else {
            return TagStreamFragment.CONFERENCE_HASHTAG;
        }
    }

    private BroadcastReceiver mPackageChangesReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        }
    };

    /**
     * {@link com.lullabot.android.apps.iosched.provider.ScheduleContract.Sessions} query parameters.
     */
    private interface SessionsQuery {
        int _TOKEN = 0x1;

        String[] PROJECTION = { ScheduleContract.Blocks.BLOCK_START, ScheduleContract.Blocks.BLOCK_END,
                ScheduleContract.Sessions.SESSION_LEVEL, ScheduleContract.Sessions.SESSION_TITLE,
                ScheduleContract.Sessions.SESSION_ABSTRACT, ScheduleContract.Sessions.SESSION_REQUIREMENTS,
                ScheduleContract.Sessions.SESSION_STARRED, ScheduleContract.Sessions.SESSION_HASHTAG,
                ScheduleContract.Sessions.SESSION_SLUG, ScheduleContract.Sessions.SESSION_URL,
                ScheduleContract.Sessions.SESSION_MODERATOR_URL, ScheduleContract.Sessions.SESSION_YOUTUBE_URL,
                ScheduleContract.Sessions.SESSION_PDF_URL, ScheduleContract.Sessions.SESSION_FEEDBACK_URL,
                ScheduleContract.Sessions.SESSION_NOTES_URL, ScheduleContract.Sessions.ROOM_ID,
                ScheduleContract.Rooms.ROOM_NAME, };

        int BLOCK_START = 0;
        int BLOCK_END = 1;
        int LEVEL = 2;
        int TITLE = 3;
        int ABSTRACT = 4;
        int REQUIREMENTS = 5;
        int STARRED = 6;
        int HASHTAG = 7;
        int SLUG = 8;
        int URL = 9;
        int MODERATOR_URL = 10;
        int YOUTUBE_URL = 11;
        int PDF_URL = 12;
        int FEEDBACK_URL = 13;
        int NOTES_URL = 14;
        int ROOM_ID = 15;
        int ROOM_NAME = 16;

        int[] LINKS_INDICES = { URL, MODERATOR_URL, YOUTUBE_URL, PDF_URL, FEEDBACK_URL, NOTES_URL, };

        int[] LINKS_TITLES = { R.string.session_link_main, R.string.session_link_moderator,
                R.string.session_link_youtube, R.string.session_link_pdf, R.string.session_link_feedback,
                R.string.session_link_notes, };
    }

    /**
     * {@link com.lullabot.android.apps.iosched.provider.ScheduleContract.Tracks} query parameters.
     */
    private interface TracksQuery {
        int _TOKEN = 0x2;

        String[] PROJECTION = { ScheduleContract.Tracks.TRACK_NAME, ScheduleContract.Tracks.TRACK_COLOR, };

        int TRACK_NAME = 0;
        int TRACK_COLOR = 1;
    }

    private interface SpeakersQuery {
        int _TOKEN = 0x3;

        String[] PROJECTION = { ScheduleContract.Speakers.SPEAKER_NAME, ScheduleContract.Speakers.SPEAKER_IMAGE_URL,
                ScheduleContract.Speakers.SPEAKER_COMPANY, ScheduleContract.Speakers.SPEAKER_ABSTRACT,
                ScheduleContract.Speakers.SPEAKER_URL, };

        int SPEAKER_NAME = 0;
        int SPEAKER_IMAGE_URL = 1;
        int SPEAKER_COMPANY = 2;
        int SPEAKER_ABSTRACT = 3;
        int SPEAKER_URL = 4;
    }
}