Java tutorial
/** * FeedEx * * Copyright (c) 2012-2013 Frederic Julian * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * Some parts of this software are based on "Sparse rss" under the MIT license (see * below). Please refers to the original project to identify which parts are under the * MIT license. * * Copyright (c) 2010-2012 Stefan Handschuh * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package co.nerdart.ourss.fragment; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.ListFragment; import android.util.Pair; import android.view.ActionMode; 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.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ListView; import android.widget.TextView; import java.io.File; import java.io.FilenameFilter; import java.util.regex.Matcher; import java.util.regex.Pattern; import co.nerdart.ourss.Constants; import co.nerdart.ourss.R; import co.nerdart.ourss.activity.GeneralPrefsActivity; import co.nerdart.ourss.adapter.FeedsCursorAdapter; import co.nerdart.ourss.parser.OPML; import co.nerdart.ourss.provider.FeedData; import co.nerdart.ourss.provider.FeedData.EntryColumns; import co.nerdart.ourss.provider.FeedData.FeedColumns; import co.nerdart.ourss.provider.FeedDataContentProvider; import co.nerdart.ourss.service.FetcherService; import co.nerdart.ourss.view.DragNDropExpandableListView; import co.nerdart.ourss.view.DragNDropListener; import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Style; //import android.widget.Toast; public class FeedsListFragment extends ListFragment { private FeedsCursorAdapter mListAdapter; private DragNDropExpandableListView mListView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mListAdapter = new FeedsCursorAdapter(getActivity(), FeedColumns.GROUPS_CONTENT_URI); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.feed_list_fragment, container, false); mListView = (DragNDropExpandableListView) rootView.findViewById(android.R.id.list); mListAdapter.setExpandableListView(mListView); mListView.setFastScrollEnabled(true); mListView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { String title = ((TextView) view.findViewById(android.R.id.text1)).getText().toString(); Matcher m = Pattern.compile("(.*) \\([0-9]+\\)$").matcher(title); if (m.matches()) { title = m.group(1); } long feedId = mListView.getItemIdAtPosition(position); ActionMode actionMode; if (view.findViewById(R.id.indicator).getVisibility() == View.VISIBLE) { // This is a group actionMode = getActivity().startActionMode(mGroupActionModeCallback); } else { // This is a feed actionMode = getActivity().startActionMode(mFeedActionModeCallback); } actionMode.setTag(new Pair<Long, String>(feedId, title)); mListAdapter.setSelectedFeed(feedId); return true; } }); mListView.setAdapter(mListAdapter); mListView.setOnChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { mListAdapter.startFeedActivity(id); return false; } }); mListView.setDragNDropListener(new DragNDropListener() { boolean fromHasGroupIndicator = false; @Override public void onStopDrag(View itemView) { } @Override public void onStartDrag(View itemView) { fromHasGroupIndicator = itemView.findViewById(R.id.indicator).getVisibility() == View.VISIBLE; } @Override public void onDrop(final int flatPosFrom, final int flatPosTo) { final boolean fromIsGroup = ExpandableListView.getPackedPositionType(mListView .getExpandableListPosition(flatPosFrom)) == ExpandableListView.PACKED_POSITION_TYPE_GROUP; final boolean toIsGroup = ExpandableListView.getPackedPositionType(mListView .getExpandableListPosition(flatPosTo)) == ExpandableListView.PACKED_POSITION_TYPE_GROUP; final boolean fromIsFeedWithoutGroup = fromIsGroup && !fromHasGroupIndicator; View toView = mListView.getChildAt(flatPosTo - mListView.getFirstVisiblePosition()); boolean toIsFeedWithoutGroup = toIsGroup && toView.findViewById(R.id.indicator).getVisibility() != View.VISIBLE; final long packedPosTo = mListView.getExpandableListPosition(flatPosTo); final int packedGroupPosTo = ExpandableListView.getPackedPositionGroup(packedPosTo); if ((fromIsFeedWithoutGroup || !fromIsGroup) && toIsGroup && !toIsFeedWithoutGroup) { new AlertDialog.Builder(getActivity()) // .setTitle(R.string.to_group_title) // .setMessage(R.string.to_group_message) // .setPositiveButton(R.string.to_group_into, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ContentValues values = new ContentValues(); values.put(FeedColumns.PRIORITY, 1); values.put(FeedColumns.GROUP_ID, mListView.getItemIdAtPosition(flatPosTo)); ContentResolver cr = getActivity().getContentResolver(); cr.update(FeedColumns.CONTENT_URI(mListView.getItemIdAtPosition(flatPosFrom)), values, null, null); cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } }).setNegativeButton(R.string.to_group_above, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { moveItem(fromIsGroup, toIsGroup, fromIsFeedWithoutGroup, packedPosTo, packedGroupPosTo, flatPosFrom); } }).show(); } else { moveItem(fromIsGroup, toIsGroup, fromIsFeedWithoutGroup, packedPosTo, packedGroupPosTo, flatPosFrom); } } @Override public void onDrag(int x, int y, ListView listView) { } }); return rootView; } private void moveItem(boolean fromIsGroup, boolean toIsGroup, boolean fromIsFeedWithoutGroup, long packedPosTo, int packedGroupPosTo, int flatPosFrom) { ContentValues values = new ContentValues(); ContentResolver cr = getActivity().getContentResolver(); if (fromIsGroup && toIsGroup) { values.put(FeedColumns.PRIORITY, packedGroupPosTo + 1); cr.update(FeedColumns.CONTENT_URI(mListView.getItemIdAtPosition(flatPosFrom)), values, null, null); cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } else if (!fromIsGroup && toIsGroup) { values.put(FeedColumns.PRIORITY, packedGroupPosTo + 1); values.putNull(FeedColumns.GROUP_ID); cr.update(FeedColumns.CONTENT_URI(mListView.getItemIdAtPosition(flatPosFrom)), values, null, null); cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } else if ((!fromIsGroup && !toIsGroup) || (fromIsFeedWithoutGroup && !toIsGroup)) { int groupPrio = ExpandableListView.getPackedPositionChild(packedPosTo) + 1; values.put(FeedColumns.PRIORITY, groupPrio); int flatGroupPosTo = mListView .getFlatListPosition(ExpandableListView.getPackedPositionForGroup(packedGroupPosTo)); values.put(FeedColumns.GROUP_ID, mListView.getItemIdAtPosition(flatGroupPosTo)); cr.update(FeedColumns.CONTENT_URI(mListView.getItemIdAtPosition(flatPosFrom)), values, null, null); cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } } @Override public void onDestroy() { getLoaderManager().destroyLoader(0); // This is needed to avoid an activity leak! super.onDestroy(); } private void setFeedSortEnabled(boolean enabled) { if (enabled != mListView.isDragNDropEnabled()) { mListAdapter.setFeedSortEnabled(enabled); mListView.setDragNDropEnabled(enabled); getActivity().invalidateOptionsMenu(); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (mListView.isDragNDropEnabled()) { inflater.inflate(R.menu.feed_overview_sorting, menu); } else { inflater.inflate(R.menu.feed_overview, menu); } super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(final MenuItem item) { setFeedSortEnabled(false); switch (item.getItemId()) { case R.id.menu_add_feed: { startActivity(new Intent(Intent.ACTION_INSERT).setData(FeedColumns.CONTENT_URI)); return true; } case R.id.menu_refresh: { if (!FetcherService.isRefreshingFeeds) { getActivity().startService( new Intent(getActivity(), FetcherService.class).setAction(Constants.ACTION_REFRESH_FEEDS)); } return true; } case R.id.menu_settings: { startActivity(new Intent(getActivity(), GeneralPrefsActivity.class)); return true; } case R.id.menu_all_read: { new Thread() { @Override public void run() { ContentResolver cr = getActivity().getContentResolver(); if (cr.update(EntryColumns.CONTENT_URI, FeedData.getReadContentValues(), EntryColumns.WHERE_UNREAD, null) > 0) { cr.notifyChange(FeedColumns.CONTENT_URI, null); cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); cr.notifyChange(EntryColumns.FAVORITES_CONTENT_URI, null); } } }.start(); return true; } case R.id.menu_add_group: { final EditText input = new EditText(getActivity()); input.setSingleLine(true); new AlertDialog.Builder(getActivity()) // .setTitle(R.string.add_group_title) // .setView(input) // // .setMessage(R.string.add_group_sentence) // .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new Thread() { @Override public void run() { String groupName = input.getText().toString(); if (!groupName.isEmpty()) { ContentResolver cr = getActivity().getContentResolver(); ContentValues values = new ContentValues(); values.put(FeedColumns.IS_GROUP, true); values.put(FeedColumns.NAME, groupName); cr.insert(FeedColumns.GROUPS_CONTENT_URI, values); } } }.start(); } }).setNegativeButton(android.R.string.cancel, null).show(); return true; } case R.id.menu_import: { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.select_file); try { final String[] fileNames = Environment.getExternalStorageDirectory().list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { return new File(dir, filename).isFile(); } }); builder.setItems(fileNames, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, final int which) { new Thread(new Runnable() { // To not block the UI @Override public void run() { try { OPML.importFromFile(Environment.getExternalStorageDirectory().toString() + File.separator + fileNames[which]); } catch (Exception e) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { Crouton.makeText(getActivity(), R.string.error_feed_import, Style.INFO); } }); } } }).start(); } }); builder.show(); } catch (Exception e) { Crouton.makeText(getActivity(), R.string.error_feed_import, Style.INFO); } } else { Crouton.makeText(getActivity(), R.string.error_external_storage_not_available, Style.INFO); } return true; } case R.id.menu_export: { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { new Thread(new Runnable() { // To not block the UI @Override public void run() { try { final String filename = Environment.getExternalStorageDirectory().toString() + "/OURSS_" + System.currentTimeMillis() + ".opml"; OPML.exportToFile(filename); getActivity().runOnUiThread(new Runnable() { @Override public void run() { //Toast.makeText(getActivity(), // String.format // (getString(R.string.message_exported_to), // filename),Toast.LENGTH_LONG).show(); Crouton.makeText(getActivity(), String.format(getString(R.string.message_exported_to), filename), Style.INFO); } }); } catch (Exception e) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { //Toast.makeText(getActivity(), // R.string.error_feed_export, // Toast.LENGTH_LONG).show(); Crouton.makeText(getActivity(), R.string.error_feed_export, Style.INFO); } }); } } }).start(); } else { //Toast.makeText(getActivity(), R.string.error_external_storage_not_available, // Toast.LENGTH_LONG).show(); Crouton.makeText(getActivity(), R.string.error_external_storage_not_available, Style.INFO); } break; } case R.id.menu_enable_feed_sort: { setFeedSortEnabled(true); return true; } case R.id.menu_disable_feed_sort: { // do nothing as the feed sort gets disabled anyway return true; } } return super.onOptionsItemSelected(item); } private final ActionMode.Callback mFeedActionModeCallback = new ActionMode.Callback() { // Called when the action mode is created; startActionMode() was called @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Inflate a menu resource providing context menu items MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.feed_context_menu, menu); return true; } // Called each time the action mode is shown. Always called after onCreateActionMode, but // may be called multiple times if the mode is invalidated. @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; // Return false if nothing is done } // Called when the user selects a contextual menu item @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { @SuppressWarnings("unchecked") Pair<Long, String> tag = (Pair<Long, String>) mode.getTag(); final long feedId = tag.first; final String title = tag.second; switch (item.getItemId()) { case R.id.menu_refresh: ConnectivityManager connectivityManager = (ConnectivityManager) getActivity() .getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); // since we have acquired the networkInfo, we use it for basic checks if (networkInfo != null && networkInfo.getState() == NetworkInfo.State.CONNECTED) { getActivity().startService(new Intent(getActivity(), FetcherService.class) .setAction(Constants.ACTION_REFRESH_FEEDS) .putExtra(Constants.FEED_ID, Long.toString(feedId))); } else { Crouton.makeText(getActivity(), R.string.network_error, Style.INFO); } mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_mark_as_read: new Thread() { @Override public void run() { String id = Long.toString(feedId); ContentResolver cr = getActivity().getContentResolver(); if (cr.update(EntryColumns.ENTRIES_FOR_FEED_CONTENT_URI(id), FeedData.getReadContentValues(), EntryColumns.WHERE_UNREAD, null) > 0) { cr.notifyChange(EntryColumns.CONTENT_URI, null); cr.notifyChange(EntryColumns.FAVORITES_CONTENT_URI, null); cr.notifyChange(FeedColumns.CONTENT_URI(id), null); FeedDataContentProvider.notifyGroupFromFeedId(id); } } }.start(); mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_edit: startActivity(new Intent(Intent.ACTION_EDIT).setData(FeedColumns.CONTENT_URI(feedId))); mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_delete: new AlertDialog.Builder(getActivity()) // .setIcon(android.R.drawable.ic_dialog_alert) // .setTitle(title) // .setMessage(R.string.question_delete_feed) // .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new Thread() { @Override public void run() { ContentResolver cr = getActivity().getContentResolver(); // First we got the groupId (it will be deleted after) Cursor feedCursor = cr.query(FeedColumns.CONTENT_URI(feedId), FeedColumns.PROJECTION_GROUP_ID, null, null, null); String groupId = null; if (feedCursor.moveToFirst()) { groupId = feedCursor.getString(0); } feedCursor.close(); // Now we delete the feed if (cr.delete(FeedColumns.CONTENT_URI(feedId), null, null) > 0) { cr.notifyChange(EntryColumns.CONTENT_URI, null); cr.notifyChange(EntryColumns.FAVORITES_CONTENT_URI, null); if (groupId == null) { cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } else { cr.notifyChange(FeedColumns.FEEDS_FOR_GROUPS_CONTENT_URI(groupId), null); } } } }.start(); } }).setNegativeButton(android.R.string.no, null).show(); mode.finish(); // Action picked, so close the CAB return true; default: return false; } } // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionMode mode) { mListAdapter.setSelectedFeed(-1); } }; private final ActionMode.Callback mGroupActionModeCallback = new ActionMode.Callback() { // Called when the action mode is created; startActionMode() was called @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Inflate a menu resource providing context menu items MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.edit_context_menu, menu); return true; } // Called each time the action mode is shown. Always called after onCreateActionMode, but // may be called multiple times if the mode is invalidated. @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; // Return false if nothing is done } // Called when the user selects a contextual menu item @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { @SuppressWarnings("unchecked") Pair<Long, String> tag = (Pair<Long, String>) mode.getTag(); final long groupId = tag.first; final String title = tag.second; switch (item.getItemId()) { case R.id.menu_edit: final EditText input = new EditText(getActivity()); input.setSingleLine(true); input.setText(title); new AlertDialog.Builder(getActivity()) // .setTitle(R.string.edit_group_title) // .setView(input) // .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new Thread() { @Override public void run() { String groupName = input.getText().toString(); if (!groupName.isEmpty()) { ContentResolver cr = getActivity().getContentResolver(); ContentValues values = new ContentValues(); values.put(FeedColumns.NAME, groupName); if (cr.update(FeedColumns.CONTENT_URI(groupId), values, null, null) > 0) { cr.notifyChange(FeedColumns.GROUPS_CONTENT_URI, null); } } } }.start(); } }).setNegativeButton(android.R.string.cancel, null).show(); mode.finish(); // Action picked, so close the CAB return true; case R.id.menu_delete: new AlertDialog.Builder(getActivity()) // .setIcon(android.R.drawable.ic_dialog_alert) // .setTitle(title) // .setMessage(R.string.question_delete_group) // .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new Thread() { @Override public void run() { ContentResolver cr = getActivity().getContentResolver(); if (cr.delete(FeedColumns.GROUPS_CONTENT_URI(groupId), null, null) > 0) { cr.notifyChange(EntryColumns.CONTENT_URI, null); cr.notifyChange(EntryColumns.FAVORITES_CONTENT_URI, null); } } }.start(); } }).setNegativeButton(android.R.string.no, null).show(); mode.finish(); // Action picked, so close the CAB return true; default: return false; } } // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionMode mode) { mListAdapter.setSelectedFeed(-1); } }; }