michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.home; michael@0: michael@0: import java.util.EnumSet; michael@0: michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.ReaderModeUtils; michael@0: import org.mozilla.gecko.Telemetry; michael@0: import org.mozilla.gecko.TelemetryContract; michael@0: import org.mozilla.gecko.db.BrowserContract.ReadingListItems; michael@0: import org.mozilla.gecko.db.BrowserDB; michael@0: import org.mozilla.gecko.db.BrowserDB.URLColumns; michael@0: import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; michael@0: michael@0: import android.app.Activity; michael@0: import android.content.Context; michael@0: import android.content.res.Configuration; michael@0: import android.database.Cursor; michael@0: import android.os.Bundle; michael@0: import android.support.v4.app.LoaderManager.LoaderCallbacks; michael@0: import android.support.v4.content.Loader; michael@0: import android.support.v4.widget.CursorAdapter; michael@0: import android.text.SpannableStringBuilder; michael@0: import android.text.Spanned; michael@0: import android.text.style.ImageSpan; michael@0: import android.view.LayoutInflater; michael@0: import android.view.View; michael@0: import android.view.ViewGroup; michael@0: import android.view.ViewStub; michael@0: import android.widget.AdapterView; michael@0: import android.widget.TextView; michael@0: michael@0: /** michael@0: * Fragment that displays reading list contents in a ListView. michael@0: */ michael@0: public class ReadingListPanel extends HomeFragment { michael@0: michael@0: // Cursor loader ID for reading list michael@0: private static final int LOADER_ID_READING_LIST = 0; michael@0: michael@0: // Formatted string in hint text to be replaced with an icon. michael@0: private final String MATCH_STRING = "%I"; michael@0: michael@0: // Adapter for the list of reading list items michael@0: private ReadingListAdapter mAdapter; michael@0: michael@0: // The view shown by the fragment michael@0: private HomeListView mList; michael@0: michael@0: // Reference to the View to display when there are no results. michael@0: private View mEmptyView; michael@0: michael@0: // Reference to top view. michael@0: private View mTopView; michael@0: michael@0: // Callbacks used for the reading list and favicon cursor loaders michael@0: private CursorLoaderCallbacks mCursorLoaderCallbacks; michael@0: michael@0: // On URL open listener michael@0: private OnUrlOpenListener mUrlOpenListener; michael@0: michael@0: @Override michael@0: public void onAttach(Activity activity) { michael@0: super.onAttach(activity); michael@0: michael@0: try { michael@0: mUrlOpenListener = (OnUrlOpenListener) activity; michael@0: } catch (ClassCastException e) { michael@0: throw new ClassCastException(activity.toString() michael@0: + " must implement HomePager.OnUrlOpenListener"); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onDetach() { michael@0: super.onDetach(); michael@0: michael@0: mUrlOpenListener = null; michael@0: } michael@0: michael@0: @Override michael@0: public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { michael@0: return inflater.inflate(R.layout.home_reading_list_panel, container, false); michael@0: } michael@0: michael@0: @Override michael@0: public void onViewCreated(View view, Bundle savedInstanceState) { michael@0: super.onViewCreated(view, savedInstanceState); michael@0: michael@0: mTopView = view; michael@0: michael@0: mList = (HomeListView) view.findViewById(R.id.list); michael@0: mList.setTag(HomePager.LIST_TAG_READING_LIST); michael@0: michael@0: mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { michael@0: @Override michael@0: public void onItemClick(AdapterView parent, View view, int position, long id) { michael@0: final Cursor c = mAdapter.getCursor(); michael@0: if (c == null || !c.moveToPosition(position)) { michael@0: return; michael@0: } michael@0: michael@0: String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); michael@0: url = ReaderModeUtils.getAboutReaderForUrl(url); michael@0: michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL); michael@0: michael@0: // This item is a TwoLinePageRow, so we allow switch-to-tab. michael@0: mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)); michael@0: } michael@0: }); michael@0: michael@0: mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() { michael@0: @Override michael@0: public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { michael@0: final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); michael@0: info.url = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.URL)); michael@0: info.title = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.TITLE)); michael@0: info.readingListItemId = cursor.getInt(cursor.getColumnIndexOrThrow(ReadingListItems._ID)); michael@0: return info; michael@0: } michael@0: }); michael@0: registerForContextMenu(mList); michael@0: } michael@0: michael@0: @Override michael@0: public void onDestroyView() { michael@0: super.onDestroyView(); michael@0: mList = null; michael@0: mTopView = null; michael@0: mEmptyView = null; michael@0: } michael@0: michael@0: @Override michael@0: public void onConfigurationChanged(Configuration newConfig) { michael@0: super.onConfigurationChanged(newConfig); michael@0: michael@0: // Detach and reattach the fragment as the layout changes. michael@0: if (isVisible()) { michael@0: getFragmentManager().beginTransaction() michael@0: .detach(this) michael@0: .attach(this) michael@0: .commitAllowingStateLoss(); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onActivityCreated(Bundle savedInstanceState) { michael@0: super.onActivityCreated(savedInstanceState); michael@0: michael@0: mAdapter = new ReadingListAdapter(getActivity(), null); michael@0: mList.setAdapter(mAdapter); michael@0: michael@0: // Create callbacks before the initial loader is started. michael@0: mCursorLoaderCallbacks = new CursorLoaderCallbacks(); michael@0: loadIfVisible(); michael@0: } michael@0: michael@0: @Override michael@0: protected void load() { michael@0: getLoaderManager().initLoader(LOADER_ID_READING_LIST, null, mCursorLoaderCallbacks); michael@0: } michael@0: michael@0: private void updateUiFromCursor(Cursor c) { michael@0: // We delay setting the empty view until the cursor is actually empty. michael@0: // This avoids image flashing. michael@0: if ((c == null || c.getCount() == 0) && mEmptyView == null) { michael@0: final ViewStub emptyViewStub = (ViewStub) mTopView.findViewById(R.id.home_empty_view_stub); michael@0: mEmptyView = emptyViewStub.inflate(); michael@0: michael@0: final TextView emptyHint = (TextView) mEmptyView.findViewById(R.id.home_empty_hint); michael@0: String readingListHint = emptyHint.getText().toString(); michael@0: michael@0: // Use an ImageSpan to include the reader icon in the "Tip". michael@0: int imageSpanIndex = readingListHint.indexOf(MATCH_STRING); michael@0: if (imageSpanIndex != -1) { michael@0: final ImageSpan readingListIcon = new ImageSpan(getActivity(), R.drawable.reader_cropped, ImageSpan.ALIGN_BOTTOM); michael@0: final SpannableStringBuilder hintBuilder = new SpannableStringBuilder(readingListHint); michael@0: michael@0: // Add additional spacing. michael@0: hintBuilder.insert(imageSpanIndex + MATCH_STRING.length(), " "); michael@0: hintBuilder.insert(imageSpanIndex, " "); michael@0: michael@0: // Add icon. michael@0: hintBuilder.setSpan(readingListIcon, imageSpanIndex + 1, imageSpanIndex + MATCH_STRING.length() + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); michael@0: michael@0: emptyHint.setText(hintBuilder, TextView.BufferType.SPANNABLE); michael@0: } michael@0: michael@0: mList.setEmptyView(mEmptyView); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Cursor loader for the list of reading list items. michael@0: */ michael@0: private static class ReadingListLoader extends SimpleCursorLoader { michael@0: public ReadingListLoader(Context context) { michael@0: super(context); michael@0: } michael@0: michael@0: @Override michael@0: public Cursor loadCursor() { michael@0: return BrowserDB.getReadingList(getContext().getContentResolver()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Cursor adapter for the list of reading list items. michael@0: */ michael@0: private class ReadingListAdapter extends CursorAdapter { michael@0: public ReadingListAdapter(Context context, Cursor cursor) { michael@0: super(context, cursor, 0); michael@0: } michael@0: michael@0: @Override michael@0: public void bindView(View view, Context context, Cursor cursor) { michael@0: final ReadingListRow row = (ReadingListRow) view; michael@0: row.updateFromCursor(cursor); michael@0: } michael@0: michael@0: @Override michael@0: public View newView(Context context, Cursor cursor, ViewGroup parent) { michael@0: return LayoutInflater.from(parent.getContext()).inflate(R.layout.reading_list_item_row, parent, false); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * LoaderCallbacks implementation that interacts with the LoaderManager. michael@0: */ michael@0: private class CursorLoaderCallbacks implements LoaderCallbacks { michael@0: @Override michael@0: public Loader onCreateLoader(int id, Bundle args) { michael@0: return new ReadingListLoader(getActivity()); michael@0: } michael@0: michael@0: @Override michael@0: public void onLoadFinished(Loader loader, Cursor c) { michael@0: mAdapter.swapCursor(c); michael@0: updateUiFromCursor(c); michael@0: } michael@0: michael@0: @Override michael@0: public void onLoaderReset(Loader loader) { michael@0: mAdapter.swapCursor(null); michael@0: } michael@0: } michael@0: }