Added basic ability to add feeds from a search.

This commit is contained in:
RyanBateman 2012-08-28 17:14:23 -04:00
parent 4f6b8f4f8a
commit ba9b67b6c1
23 changed files with 539 additions and 1 deletions

View file

@ -30,7 +30,8 @@
<activity
android:name=".activity.Main"
android:label="@string/newsblur" />
android:label="@string/newsblur">
</activity>
<activity
android:name=".activity.Profile"
@ -57,6 +58,14 @@
<activity
android:name=".activity.FolderReading"/>
<activity
android:name=".activity.SearchForFeeds" android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>
<activity
android:name=".activity.SocialFeedReading"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background" >
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/empty_search_notice"
android:textColor="@color/darkgray"
android:textSize="13dp"
android:textStyle="italic" />
<ListView
android:id="@+id/feed_result_list"
android:name="com.newsblur.fragment.FolderListFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<View
android:layout_width="fill_parent"
android:layout_height="8dip"
android:layout_alignParentTop="true"
android:background="@drawable/orangeline_shadow" />
</RelativeLayout>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
<TextView
android:id="@+id/dialog_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="3"
android:padding="10dp" />
<Button
android:id="@+id/dialog_button_okay"
style="@style/greenButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/dialog_message"
android:text="@string/alert_dialog_ok"
android:textSize="14sp" />
<Button
android:id="@+id/dialog_button_cancel"
style="@style/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/dialog_message"
android:padding="10dp"
android:text="@string/alert_dialog_cancel"
android:textSize="14sp" />
</RelativeLayout>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/item_background"
android:orientation="horizontal" >
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent" >
<View
android:id="@+id/row_result_favicon_color"
android:layout_width="8dp"
android:layout_height="match_parent" />
<TextView
android:id="@+id/row_item_sidebar"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="12dp"
android:layout_toRightOf="@id/row_result_favicon_color" />
<ImageView
android:id="@+id/row_result_feedicon"
android:layout_width="21dp"
android:layout_height="21dp"
android:layout_marginTop="10dp"
android:background="@drawable/white_background_circle"
android:paddingBottom="4dp"
android:paddingTop="2dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:layout_toRightOf="@id/row_item_sidebar" />
<TextView
android:id="@+id/row_result_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="12dp"
android:layout_toRightOf="@id/row_result_feedicon"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/darkgray"
android:textSize="11dp" />
<TextView
android:id="@+id/row_result_tagline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/row_result_feedicon"
android:layout_below="@id/row_result_feedicon"
android:layout_marginRight="8dp"
android:paddingTop="8dp"
android:paddingBottom="12dp"
android:textColor="@color/darkgray"
android:textSize="20dp" />
</RelativeLayout>
</LinearLayout>

View file

@ -11,5 +11,10 @@
android:icon="@drawable/person"
android:title="@string/menu_profile"
android:showAsAction="ifRoom" />
<item android:id="@+id/menu_add_feed"
android:icon="@drawable/plus"
android:title="@string/menu_add_feed"
android:showAsAction="never" />
</menu>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_search"
android:icon="@drawable/search"
android:title="@string/menu_search"
android:showAsAction="always" />
</menu>

View file

@ -16,6 +16,9 @@
<string name="hello">Hello World!</string>
<string name="title_feed_search">Search for feeds</string>
<string name="add_feed_message">Add \"%s\" to your feeds?</string>
<string name="dialog_title">Alert</string>
<string name="loading">Loading…</string>
@ -82,5 +85,9 @@
<string name="unsorted_folder_name">Unsorted</string>
<string name="empty_list_notice">There aren\'t any stories to display here right now.</string>
<string name="feed_search_hint">Search for new feeds</string>
<string name="menu_add_feed">Add new feed</string>
<string name="menu_search">Search</string>
<string name="empty_search_notice">Type a search term to begin</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/newsblur"
android:hint="@string/feed_search_hint" >
</searchable>

View file

@ -0,0 +1,7 @@
package com.newsblur.activity;
import com.actionbarsherlock.app.SherlockFragmentActivity;
public class FeedSearch extends SherlockFragmentActivity {
}

View file

@ -0,0 +1,61 @@
package com.newsblur.activity;
import java.util.List;
import com.newsblur.R;
import com.newsblur.domain.FeedResult;
import com.newsblur.util.UIUtils;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import android.util.Base64;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class FeedSearchResultAdapter extends ArrayAdapter<FeedResult>{
private LayoutInflater inflater;
private Context context;
public FeedSearchResultAdapter(Context context, int resource, int textViewResourceId, List<FeedResult> items) {
super(context, resource, textViewResourceId, items);
this.context = context;
inflater = ((Activity) context).getLayoutInflater();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView != null) {
v = convertView;
} else {
v = inflater.inflate(R.layout.row_feedresult, null);
}
FeedResult result = getItem(position);
ImageView favicon = (ImageView) v.findViewById(R.id.row_result_feedicon);
Bitmap bitmap = null;
if (!TextUtils.isEmpty(result.favicon)) {
final byte[] data = Base64.decode(result.favicon, Base64.DEFAULT);
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
}
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.no_favicon);
}
favicon.setImageBitmap(UIUtils.roundBitmap(bitmap));
((TextView) v.findViewById(R.id.row_result_title)).setText(result.label);
((TextView) v.findViewById(R.id.row_result_tagline)).setText(result.tagline);
return v;
}
}

View file

@ -95,6 +95,10 @@ public class Main extends SherlockFragmentActivity implements StateChangedListen
case R.id.menu_refresh:
triggerRecount();
return true;
case R.id.menu_add_feed:
Intent intent = new Intent(this, SearchForFeeds.class);
startActivityForResult(intent, 0);
return true;
}
return super.onOptionsItemSelected(item);
}

View file

@ -0,0 +1,124 @@
package com.newsblur.activity;
import java.util.ArrayList;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.newsblur.R;
import com.newsblur.domain.FeedResult;
import com.newsblur.fragment.AddFeedFragment;
import com.newsblur.network.SearchAsyncTaskLoader;
public class SearchForFeeds extends SherlockFragmentActivity implements LoaderCallbacks<ArrayList<FeedResult>>, OnItemClickListener {
private static final int LOADER_TWITTER_SEARCH = 0x01;
private Menu menu;
private ListView resultsList;
private Loader<ArrayList<FeedResult>> searchLoader;
private FeedSearchResultAdapter adapter;
@Override
protected void onCreate(Bundle arg0) {
requestWindowFeature(Window.FEATURE_PROGRESS);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(arg0);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle(R.string.title_feed_search);
setContentView(R.layout.activity_feed_search);
TextView emptyView = (TextView) findViewById(R.id.empty_view);
resultsList = (ListView) findViewById(R.id.feed_result_list);
resultsList.setEmptyView(emptyView);
resultsList.setOnItemClickListener(this);
searchLoader = getSupportLoaderManager().initLoader(LOADER_TWITTER_SEARCH, new Bundle(), this);
onSearchRequested();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.search, menu);
this.menu = menu;
return true;
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
setSupportProgressBarIndeterminateVisibility(true);
Bundle bundle = new Bundle();
bundle.putString(SearchAsyncTaskLoader.SEARCH_TERM, query);
searchLoader = getSupportLoaderManager().restartLoader(LOADER_TWITTER_SEARCH, bundle, this);
searchLoader.forceLoad();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_search:
onSearchRequested();
return true;
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public Loader<ArrayList<FeedResult>> onCreateLoader(int loaderId, Bundle bundle) {
String searchTerm = bundle.getString(SearchAsyncTaskLoader.SEARCH_TERM);
return new SearchAsyncTaskLoader(this, searchTerm);
}
@Override
public void onLoadFinished(Loader<ArrayList<FeedResult>> loader, ArrayList<FeedResult> results) {
adapter = new FeedSearchResultAdapter(this, 0, 0, results);
resultsList.setAdapter(adapter);
setSupportProgressBarIndeterminateVisibility(false);
}
@Override
public void onLoaderReset(Loader<ArrayList<FeedResult>> loader) {
}
@Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
FeedResult result = adapter.getItem(position);
DialogFragment addFeedFragment = AddFeedFragment.newInstance(result.url, result.label);
addFeedFragment.show(getSupportFragmentManager(), "dialog");
}
}

View file

@ -0,0 +1,24 @@
package com.newsblur.domain;
import com.google.gson.annotations.SerializedName;
public class FeedResult {
@SerializedName("num_subscribers")
public int numberOfSubscriber;
@SerializedName("favicon_color")
public String faviconColor;
@SerializedName("value")
public String url;
public String tagline;
public String label;
public String id;
public String favicon;
}

View file

@ -0,0 +1,84 @@
package com.newsblur.fragment;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.network.APIManager;
public class AddFeedFragment extends DialogFragment {
private static final String FEED_ID = "feed_url";
private static final String FEED_NAME = "feed_name";
private APIManager apiManager;
public static AddFeedFragment newInstance(final String feedId, final String feedName) {
AddFeedFragment frag = new AddFeedFragment();
Bundle args = new Bundle();
args.putString(FEED_ID, feedId);
args.putString(FEED_NAME, feedName);
frag.setArguments(args);
return frag;
}
@Override
public void onCreate(Bundle savedInstanceState) {
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.dialog);
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
final String addFeedString = getResources().getString(R.string.add_feed_message);
apiManager = new APIManager(getActivity());
View v = inflater.inflate(R.layout.fragment_confirm_dialog, container, false);
final TextView message = (TextView) v.findViewById(R.id.dialog_message);
message.setText(String.format(addFeedString, getArguments().getString(FEED_NAME)));
Button okayButton = (Button) v.findViewById(R.id.dialog_button_okay);
okayButton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
v.setEnabled(false);
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
return apiManager.addFeed(getArguments().getString(FEED_ID), null);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
AddFeedFragment.this.dismiss();
AddFeedFragment.this.getActivity().finish();
} else {
AddFeedFragment.this.dismiss();
Toast.makeText(getActivity(), "Error adding feed", Toast.LENGTH_SHORT).show();
}
};
}.execute();
}
});
Button cancelButton = (Button) v.findViewById(R.id.dialog_button_cancel);
cancelButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
AddFeedFragment.this.dismiss();
}
});
return v;
}
}

View file

@ -21,9 +21,12 @@ public class APIConstants {
public static final String URL_MARK_SOCIALSTORY_AS_READ = "http://newsblur.com/reader/mark_social_stories_as_read/";
public static final String URL_SHARE_STORY = "http://newsblur.com/social/share_story";
public static final String URL_FEED_AUTOCOMPLETE = "http://newsblur.com/rss_feeds/feed_autocomplete";
public static final String URL_LIKE_COMMENT = "http://www.newsblur.com/social/like_comment";
public static final String URL_UNLIKE_COMMENT = "http://www.newsblur.com/social/remove_like_comment";
public static final String URL_REPLY_TO = "http://www.newsblur.com/social/save_comment_reply";
public static final String URL_ADD_FEED = "http://www.newsblur.com/reader/add_url";
public static final String PARAMETER_FEEDS = "feeds";
public static final String PARAMETER_PASSWORD = "password";
@ -32,6 +35,8 @@ public class APIConstants {
public static final String PARAMETER_USERID = "user_id";
public static final String PARAMETER_STORYID = "story_id";
public static final String PARAMETER_FEEDS_STORIES = "feeds_stories";
public static final String PARAMETER_FEED_SEARCH_TERM = "term";
public static final String PARAMETER_FOLDER = "folder";
public static final String PARAMETER_COMMENT_USERID = "comment_user_id";
public static final String PARAMETER_FEEDID = "feed_id";
public static final String PARAMETER_REPLY_TEXT = "reply_comments";
@ -39,10 +44,16 @@ public class APIConstants {
public static final String PARAMETER_SHARE_COMMENT = "comments";
public static final String PARAMETER_SHARE_SOURCEID = "source_user_id";
public static final String PARAMETER_MARKSOCIAL_JSON = "users_feeds_stories";
public static final String PARAMETER_URL = "url";
public static final String PARAMETER_PAGE_NUMBER = "page";
public static final String NEWSBLUR_URL = "http://www.newsblur.com";
}

View file

@ -1,5 +1,6 @@
package com.newsblur.network;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -15,11 +16,13 @@ import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Comment;
import com.newsblur.domain.Feed;
import com.newsblur.domain.FeedResult;
import com.newsblur.domain.FolderStructure;
import com.newsblur.domain.Reply;
import com.newsblur.domain.SocialFeed;
@ -390,5 +393,29 @@ public class APIManager {
return false;
}
}
public boolean addFeed(String feedUrl, String folderName) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_URL, feedUrl);
if (!TextUtils.isEmpty(folderName)) {
values.put(APIConstants.PARAMETER_FOLDER, folderName);
}
final APIResponse response = client.post(APIConstants.URL_ADD_FEED, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
}
public FeedResult[] searchForFeed(String searchTerm) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEED_SEARCH_TERM, searchTerm);
final APIResponse response = client.get(APIConstants.URL_FEED_AUTOCOMPLETE, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
return gson.fromJson(response.responseString, FeedResult[].class);
} else {
return null;
}
}
}

View file

@ -0,0 +1,33 @@
package com.newsblur.network;
import java.util.ArrayList;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import com.newsblur.domain.FeedResult;
public class SearchAsyncTaskLoader extends AsyncTaskLoader<ArrayList<FeedResult>> {
public static final String SEARCH_TERM = "searchTerm";
private String searchTerm;
private APIManager apiManager;
public SearchAsyncTaskLoader(Context context, String searchTerm) {
super(context);
this.searchTerm = searchTerm;
apiManager = new APIManager(context);
}
@Override
public ArrayList<FeedResult> loadInBackground() {
ArrayList<FeedResult> list = new ArrayList<FeedResult>();
for (FeedResult result : apiManager.searchForFeed(searchTerm)) {
list.add(result);
}
return list;
}
}