1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/home/ReadingListPanel.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,252 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.home; 1.10 + 1.11 +import java.util.EnumSet; 1.12 + 1.13 +import org.mozilla.gecko.R; 1.14 +import org.mozilla.gecko.ReaderModeUtils; 1.15 +import org.mozilla.gecko.Telemetry; 1.16 +import org.mozilla.gecko.TelemetryContract; 1.17 +import org.mozilla.gecko.db.BrowserContract.ReadingListItems; 1.18 +import org.mozilla.gecko.db.BrowserDB; 1.19 +import org.mozilla.gecko.db.BrowserDB.URLColumns; 1.20 +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; 1.21 + 1.22 +import android.app.Activity; 1.23 +import android.content.Context; 1.24 +import android.content.res.Configuration; 1.25 +import android.database.Cursor; 1.26 +import android.os.Bundle; 1.27 +import android.support.v4.app.LoaderManager.LoaderCallbacks; 1.28 +import android.support.v4.content.Loader; 1.29 +import android.support.v4.widget.CursorAdapter; 1.30 +import android.text.SpannableStringBuilder; 1.31 +import android.text.Spanned; 1.32 +import android.text.style.ImageSpan; 1.33 +import android.view.LayoutInflater; 1.34 +import android.view.View; 1.35 +import android.view.ViewGroup; 1.36 +import android.view.ViewStub; 1.37 +import android.widget.AdapterView; 1.38 +import android.widget.TextView; 1.39 + 1.40 +/** 1.41 + * Fragment that displays reading list contents in a ListView. 1.42 + */ 1.43 +public class ReadingListPanel extends HomeFragment { 1.44 + 1.45 + // Cursor loader ID for reading list 1.46 + private static final int LOADER_ID_READING_LIST = 0; 1.47 + 1.48 + // Formatted string in hint text to be replaced with an icon. 1.49 + private final String MATCH_STRING = "%I"; 1.50 + 1.51 + // Adapter for the list of reading list items 1.52 + private ReadingListAdapter mAdapter; 1.53 + 1.54 + // The view shown by the fragment 1.55 + private HomeListView mList; 1.56 + 1.57 + // Reference to the View to display when there are no results. 1.58 + private View mEmptyView; 1.59 + 1.60 + // Reference to top view. 1.61 + private View mTopView; 1.62 + 1.63 + // Callbacks used for the reading list and favicon cursor loaders 1.64 + private CursorLoaderCallbacks mCursorLoaderCallbacks; 1.65 + 1.66 + // On URL open listener 1.67 + private OnUrlOpenListener mUrlOpenListener; 1.68 + 1.69 + @Override 1.70 + public void onAttach(Activity activity) { 1.71 + super.onAttach(activity); 1.72 + 1.73 + try { 1.74 + mUrlOpenListener = (OnUrlOpenListener) activity; 1.75 + } catch (ClassCastException e) { 1.76 + throw new ClassCastException(activity.toString() 1.77 + + " must implement HomePager.OnUrlOpenListener"); 1.78 + } 1.79 + } 1.80 + 1.81 + @Override 1.82 + public void onDetach() { 1.83 + super.onDetach(); 1.84 + 1.85 + mUrlOpenListener = null; 1.86 + } 1.87 + 1.88 + @Override 1.89 + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 1.90 + return inflater.inflate(R.layout.home_reading_list_panel, container, false); 1.91 + } 1.92 + 1.93 + @Override 1.94 + public void onViewCreated(View view, Bundle savedInstanceState) { 1.95 + super.onViewCreated(view, savedInstanceState); 1.96 + 1.97 + mTopView = view; 1.98 + 1.99 + mList = (HomeListView) view.findViewById(R.id.list); 1.100 + mList.setTag(HomePager.LIST_TAG_READING_LIST); 1.101 + 1.102 + mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { 1.103 + @Override 1.104 + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1.105 + final Cursor c = mAdapter.getCursor(); 1.106 + if (c == null || !c.moveToPosition(position)) { 1.107 + return; 1.108 + } 1.109 + 1.110 + String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); 1.111 + url = ReaderModeUtils.getAboutReaderForUrl(url); 1.112 + 1.113 + Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL); 1.114 + 1.115 + // This item is a TwoLinePageRow, so we allow switch-to-tab. 1.116 + mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)); 1.117 + } 1.118 + }); 1.119 + 1.120 + mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() { 1.121 + @Override 1.122 + public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { 1.123 + final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); 1.124 + info.url = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.URL)); 1.125 + info.title = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.TITLE)); 1.126 + info.readingListItemId = cursor.getInt(cursor.getColumnIndexOrThrow(ReadingListItems._ID)); 1.127 + return info; 1.128 + } 1.129 + }); 1.130 + registerForContextMenu(mList); 1.131 + } 1.132 + 1.133 + @Override 1.134 + public void onDestroyView() { 1.135 + super.onDestroyView(); 1.136 + mList = null; 1.137 + mTopView = null; 1.138 + mEmptyView = null; 1.139 + } 1.140 + 1.141 + @Override 1.142 + public void onConfigurationChanged(Configuration newConfig) { 1.143 + super.onConfigurationChanged(newConfig); 1.144 + 1.145 + // Detach and reattach the fragment as the layout changes. 1.146 + if (isVisible()) { 1.147 + getFragmentManager().beginTransaction() 1.148 + .detach(this) 1.149 + .attach(this) 1.150 + .commitAllowingStateLoss(); 1.151 + } 1.152 + } 1.153 + 1.154 + @Override 1.155 + public void onActivityCreated(Bundle savedInstanceState) { 1.156 + super.onActivityCreated(savedInstanceState); 1.157 + 1.158 + mAdapter = new ReadingListAdapter(getActivity(), null); 1.159 + mList.setAdapter(mAdapter); 1.160 + 1.161 + // Create callbacks before the initial loader is started. 1.162 + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); 1.163 + loadIfVisible(); 1.164 + } 1.165 + 1.166 + @Override 1.167 + protected void load() { 1.168 + getLoaderManager().initLoader(LOADER_ID_READING_LIST, null, mCursorLoaderCallbacks); 1.169 + } 1.170 + 1.171 + private void updateUiFromCursor(Cursor c) { 1.172 + // We delay setting the empty view until the cursor is actually empty. 1.173 + // This avoids image flashing. 1.174 + if ((c == null || c.getCount() == 0) && mEmptyView == null) { 1.175 + final ViewStub emptyViewStub = (ViewStub) mTopView.findViewById(R.id.home_empty_view_stub); 1.176 + mEmptyView = emptyViewStub.inflate(); 1.177 + 1.178 + final TextView emptyHint = (TextView) mEmptyView.findViewById(R.id.home_empty_hint); 1.179 + String readingListHint = emptyHint.getText().toString(); 1.180 + 1.181 + // Use an ImageSpan to include the reader icon in the "Tip". 1.182 + int imageSpanIndex = readingListHint.indexOf(MATCH_STRING); 1.183 + if (imageSpanIndex != -1) { 1.184 + final ImageSpan readingListIcon = new ImageSpan(getActivity(), R.drawable.reader_cropped, ImageSpan.ALIGN_BOTTOM); 1.185 + final SpannableStringBuilder hintBuilder = new SpannableStringBuilder(readingListHint); 1.186 + 1.187 + // Add additional spacing. 1.188 + hintBuilder.insert(imageSpanIndex + MATCH_STRING.length(), " "); 1.189 + hintBuilder.insert(imageSpanIndex, " "); 1.190 + 1.191 + // Add icon. 1.192 + hintBuilder.setSpan(readingListIcon, imageSpanIndex + 1, imageSpanIndex + MATCH_STRING.length() + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 1.193 + 1.194 + emptyHint.setText(hintBuilder, TextView.BufferType.SPANNABLE); 1.195 + } 1.196 + 1.197 + mList.setEmptyView(mEmptyView); 1.198 + } 1.199 + } 1.200 + 1.201 + /** 1.202 + * Cursor loader for the list of reading list items. 1.203 + */ 1.204 + private static class ReadingListLoader extends SimpleCursorLoader { 1.205 + public ReadingListLoader(Context context) { 1.206 + super(context); 1.207 + } 1.208 + 1.209 + @Override 1.210 + public Cursor loadCursor() { 1.211 + return BrowserDB.getReadingList(getContext().getContentResolver()); 1.212 + } 1.213 + } 1.214 + 1.215 + /** 1.216 + * Cursor adapter for the list of reading list items. 1.217 + */ 1.218 + private class ReadingListAdapter extends CursorAdapter { 1.219 + public ReadingListAdapter(Context context, Cursor cursor) { 1.220 + super(context, cursor, 0); 1.221 + } 1.222 + 1.223 + @Override 1.224 + public void bindView(View view, Context context, Cursor cursor) { 1.225 + final ReadingListRow row = (ReadingListRow) view; 1.226 + row.updateFromCursor(cursor); 1.227 + } 1.228 + 1.229 + @Override 1.230 + public View newView(Context context, Cursor cursor, ViewGroup parent) { 1.231 + return LayoutInflater.from(parent.getContext()).inflate(R.layout.reading_list_item_row, parent, false); 1.232 + } 1.233 + } 1.234 + 1.235 + /** 1.236 + * LoaderCallbacks implementation that interacts with the LoaderManager. 1.237 + */ 1.238 + private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> { 1.239 + @Override 1.240 + public Loader<Cursor> onCreateLoader(int id, Bundle args) { 1.241 + return new ReadingListLoader(getActivity()); 1.242 + } 1.243 + 1.244 + @Override 1.245 + public void onLoadFinished(Loader<Cursor> loader, Cursor c) { 1.246 + mAdapter.swapCursor(c); 1.247 + updateUiFromCursor(c); 1.248 + } 1.249 + 1.250 + @Override 1.251 + public void onLoaderReset(Loader<Cursor> loader) { 1.252 + mAdapter.swapCursor(null); 1.253 + } 1.254 + } 1.255 +}