Added basic reading-item carousel for feed-specific reading lists, including ability to load and save items, view originals, cache content, and load custom CSS stylesheet for the content.

This commit is contained in:
RyanBateman 2012-07-16 14:34:33 -04:00
parent e20d58a949
commit 7212604e7a
31 changed files with 475 additions and 84 deletions

View file

@ -34,6 +34,9 @@
<activity
android:name=".activity.Profile"
android:label="@string/profile"/>
<activity
android:name=".activity.Reading"/>
<service android:name=".service.SyncService" />

View file

@ -0,0 +1,8 @@
body {
font-size: 2.0em;
padding: 5%;
}
img {
max-width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >
<solid android:color="@color/negative"/>
<stroke android:width="0.5dp" android:color="@color/darkgray"/>
</shape>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >
<solid android:color="@color/neutral" />
<stroke android:width="0.5dp" android:color="@color/darkgray"/>
</shape>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >
<solid android:color="@color/positive"/>
<stroke android:width="0.5dp" android:color="@color/darkgray"/>
</shape>

View file

@ -0,0 +1,14 @@
<?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="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="4dip" >
<android.support.v4.view.ViewPager
android:id="@+id/reading_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View file

@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/darkgray"
android:background="@drawable/list_background"
android:orientation="vertical" >
<TextView
@ -19,7 +19,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
android:divider="@drawable/divider_dark"
android:dividerHeight="1px" />
android:divider="@color/transparent"
android:dividerHeight="1dp" />
</LinearLayout>

View file

@ -0,0 +1,13 @@
<?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="match_parent"
android:background="@drawable/list_background"
android:orientation="vertical" >
<WebView
android:id="@+id/reading_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View file

@ -36,8 +36,8 @@
<TextView
android:id="@+id/row_foldersumneg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/negative_count_circle"
android:gravity="center"
@ -47,8 +47,8 @@
<TextView
android:id="@+id/row_foldersumneu"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/neutral_count_circle"
android:gravity="center"
@ -58,8 +58,8 @@
<TextView
android:id="@+id/row_foldersumpos"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/positive_count_circle"
android:gravity="center"
android:layout_marginRight="10dp"

View file

@ -1,40 +0,0 @@
<?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="@color/lightgray" >
<ImageView
android:id="@+id/row_folder_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentLeft="true"
android:layout_margin="10dp"
android:contentDescription="@string/description_row_folder_icon"
android:src="@drawable/folder" />
<TextView
android:id="@+id/row_foldername"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_toRightOf="@id/row_folder_icon"
android:textColor="@color/darkgray"
android:textSize="16dp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="35dp" >
<TextView
android:id="@+id/row_foldersumpos"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:textColor="@color/darkgray"
android:textSize="5dp"
android:textStyle="bold" />
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_reading_original"
android:icon="@drawable/website"
android:title="@string/menu_original"
android:showAsAction="always" />
<item android:id="@+id/menu_shared"
android:icon="@drawable/share"
android:title="@string/menu_share"
android:showAsAction="always" />
</menu>

View file

@ -26,6 +26,8 @@
<string name="menu_profile">Profile</string>
<string name="menu_refresh">Refresh</string>
<string name="menu_original">View original</string>
<string name="menu_share">Share</string>
<string name="profile">Profile</string>
<string name="profile_location_icon">Location icon</string>
@ -42,6 +44,4 @@
<string name="profile_with_comment">with the comment</string>
<string name="profile_shared_story">Shared the story</string>
</resources>

View file

@ -1,15 +1,167 @@
package com.newsblur.activity;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.newsblur.R;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.ReadingAdapter;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.service.DetachableResultReceiver;
import com.newsblur.service.DetachableResultReceiver.Receiver;
import com.newsblur.service.SyncService;
import com.newsblur.util.UIUtils;
public class Reading extends SherlockFragmentActivity {
public static final String EXTRA_FEED = "feed_selected";
public static final String TAG = "ReadingActivity";
private ViewPager pager;
private SyncReadingUpdaterFragment syncFragment;
private FragmentManager fragmentManager;
private ReadingAdapter readingAdapter;
private String feedId;
private final int READING_LOADER = 0x01;
private ContentResolver contentResolver;
private Feed feed;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setContentView(R.layout.activity_reading);
fragmentManager = getSupportFragmentManager();
feedId = getIntent().getStringExtra(EXTRA_FEED);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
readingAdapter = new ReadingAdapter(fragmentManager, this, feedId);
getSupportLoaderManager().initLoader(READING_LOADER , null, readingAdapter);
contentResolver = getContentResolver();
final Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
feed = Feed.fromCursor(contentResolver.query(feedUri, null, null, null, null));
setTitle(feed.title);
if (!TextUtils.isEmpty(feed.favicon)) {
BitmapFactory.Options options = new BitmapFactory.Options();
final byte[] data = Base64.decode(feed.favicon, Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
bitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true);
bitmap = UIUtils.roundCorners(bitmap, 15);
getSupportActionBar().setLogo(new BitmapDrawable(bitmap));
}
syncFragment = (SyncReadingUpdaterFragment) fragmentManager.findFragmentByTag(SyncReadingUpdaterFragment.TAG);
if (syncFragment == null) {
syncFragment = new SyncReadingUpdaterFragment();
fragmentManager.beginTransaction().add(syncFragment, SyncReadingUpdaterFragment.TAG).commit();
triggerRefresh();
}
pager = (ViewPager) findViewById(R.id.reading_pager);
pager.setAdapter(readingAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.reading, menu);
return true;
}
public void triggerRefresh() {
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_FEED_UPDATE);
intent.putExtra(SyncService.EXTRA_TASK_FEED_ID, feedId);
startService(intent);
}
public void redrawUI() {
Log.d(TAG, "Redrawing reading pager...");
getSupportLoaderManager().restartLoader(READING_LOADER, null, readingAdapter);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.menu_reading_original:
int currentItem = pager.getCurrentItem();
Story story = readingAdapter.getStory(currentItem);
if (story != null) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(story.permalink));
startActivity(i);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public static class SyncReadingUpdaterFragment extends Fragment implements Receiver {
public static final String TAG = "SyncReadingFragment";
private DetachableResultReceiver receiver;
public SyncReadingUpdaterFragment() {
receiver = new DetachableResultReceiver(new Handler());
receiver.setReceiver(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
Log.d(TAG, "Creating syncfragment");
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d(TAG, "Attached");
}
@Override
public void onReceiverResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case SyncService.STATUS_FINISHED:
Log.d(TAG, "Synchronisation finished.");
((Reading) getActivity()).redrawUI();
break;
case SyncService.STATUS_RUNNING:
Log.d(TAG, "Synchronisation running.");
break;
default:
Log.e(TAG, "Unrecognised response attempting to get reading data");
break;
}
}
}
}

View file

@ -15,6 +15,7 @@ public class FeedProvider extends ContentProvider {
public static final String VERSION = "v1";
public static final Uri NEWSBLUR_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION);
public static final Uri FEEDS_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/feeds/");
public static final Uri STORIES_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/stories/");
public static final Uri FEED_FOLDER_MAP_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/feedfoldermap/");
public static final Uri FOLDERS_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/folders/");
@ -25,11 +26,12 @@ public class FeedProvider extends ContentProvider {
private static final String TAG = "FeedProvider";
private static final int ALL_FEEDS = 0;
private static final int SPECIFIC_FEED = 1;
private static final int FEED_STORIES = 1;
private static final int ALL_FOLDERS = 2;
private static final int SPECIFIC_FOLDER = 3;
private static final int FEED_FOLDER_MAP = 4;
private static final int SPECIFIC_FEED_FOLDER_MAP = 5;
private static final int INDIVIDUAL_FEED = 6;
private BlurDatabase databaseHelper;
@ -37,7 +39,8 @@ public class FeedProvider extends ContentProvider {
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, VERSION + "/feeds/", ALL_FEEDS);
uriMatcher.addURI(AUTHORITY, VERSION + "/feeds/*/", SPECIFIC_FEED);
uriMatcher.addURI(AUTHORITY, VERSION + "/feeds/*/", INDIVIDUAL_FEED);
uriMatcher.addURI(AUTHORITY, VERSION + "/stories/#/", FEED_STORIES);
uriMatcher.addURI(AUTHORITY, VERSION + "/feedfoldermap/", FEED_FOLDER_MAP);
uriMatcher.addURI(AUTHORITY, VERSION + "/feedfoldermap/*/", SPECIFIC_FEED_FOLDER_MAP);
uriMatcher.addURI(AUTHORITY, VERSION + "/folders/", ALL_FOLDERS);
@ -88,12 +91,13 @@ public class FeedProvider extends ContentProvider {
break;
// Inserting a story
case SPECIFIC_FEED:
case FEED_STORIES:
db.beginTransaction();
db.insertWithOnConflict(DatabaseConstants.STORY_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
db.setTransactionSuccessful();
db.endTransaction();
break;
break;
case UriMatcher.NO_MATCH:
Log.d(TAG, "No match found for URI: " + uri.toString());
break;
@ -124,14 +128,20 @@ public class FeedProvider extends ContentProvider {
" ORDER BY " + DatabaseConstants.FEED_TABLE + "." + DatabaseConstants.FEED_TITLE + " COLLATE NOCASE", selectionArgs);
break;
// Querying for a specific
case SPECIFIC_FEED:
selection = DatabaseConstants.FEED_ID + " = ?";
// Query for a specific feed
case INDIVIDUAL_FEED:
cursor = db.rawQuery("SELECT " + TextUtils.join(",", DatabaseConstants.FEED_COLUMNS) + " FROM " + DatabaseConstants.FEED_TABLE +
" WHERE " + DatabaseConstants.FEED_ID + "= '" + uri.getLastPathSegment() + "'", selectionArgs);
break;
// Querying for a stories from a feed
case FEED_STORIES:
selection = DatabaseConstants.STORY_FEED_ID + " = ?";
selectionArgs = new String[] { uri.getLastPathSegment() };
cursor = db.query(DatabaseConstants.FEED_TABLE, projection, selection, selectionArgs, null, null, sortOrder);
cursor = db.query(DatabaseConstants.STORY_TABLE, null, selection, selectionArgs, null, null, null);
break;
// Query for feeds with no folder mapping
// Query for feeds with no folder mapping
case FEED_FOLDER_MAP:
String nullFolderQuery = "SELECT " + TextUtils.join(",", DatabaseConstants.FEED_COLUMNS) + " FROM " + DatabaseConstants.FEED_TABLE +
" LEFT JOIN " + DatabaseConstants.FEED_FOLDER_MAP_TABLE +
@ -148,7 +158,7 @@ public class FeedProvider extends ContentProvider {
cursor = db.rawQuery(nullFolderBuilder.toString(), null);
break;
// Querying for feeds for a given folder
// Querying for feeds for a given folder
case SPECIFIC_FEED_FOLDER_MAP:
selection = DatabaseConstants.FOLDER_ID + " = ?";
String[] folderArguments = new String[] { uri.getLastPathSegment() };
@ -168,7 +178,7 @@ public class FeedProvider extends ContentProvider {
cursor = db.rawQuery(builder.toString(), folderArguments);
break;
// Querying for all folders with unread items
// Querying for all folders with unread items
case ALL_FOLDERS:
String folderQuery = "SELECT " + TextUtils.join(",", DatabaseConstants.FOLDER_COLUMNS) + " FROM " + DatabaseConstants.FEED_FOLDER_MAP_TABLE +
" LEFT JOIN " + DatabaseConstants.FOLDER_TABLE +
@ -192,7 +202,6 @@ public class FeedProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}

View file

@ -32,4 +32,5 @@ public class FolderTreeAdapter extends SimpleCursorTreeAdapter {
return resolver.query(uri, null, null, new String[] { currentState }, null);
}
}

View file

@ -0,0 +1,79 @@
package com.newsblur.database;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import com.newsblur.domain.Story;
import com.newsblur.fragment.ReadingItemFragment;
public class ReadingAdapter extends FragmentStatePagerAdapter implements LoaderManager.LoaderCallbacks<Cursor> {
private Context context;
private Cursor cursor;
private Uri feedUri;
private String TAG = "ReadingAdapter";
public ReadingAdapter(final FragmentManager fragmentManager, final Context context, final String feedId) {
super(fragmentManager);
this.context = context;
feedUri = FeedProvider.STORIES_URI.buildUpon().appendPath(feedId).build();
}
@Override
public Fragment getItem(int position) {
if (cursor == null) {
return null;
} else {
cursor.moveToPosition(position);
return new ReadingItemFragment(Story.fromCursor(cursor));
}
}
@Override
public int getCount() {
if (cursor != null) {
return cursor.getCount();
} else {
Log.d(TAG , "No cursor");
return 0;
}
}
public Story getStory(int position) {
if (cursor == null || position > cursor.getCount()) {
return null;
} else {
cursor.moveToPosition(position);
return Story.fromCursor(cursor);
}
}
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
CursorLoader cursorLoader = new CursorLoader(context, feedUri, null, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
this.cursor = cursor;
notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
notifyDataSetChanged();
}
}

View file

@ -1,6 +1,7 @@
package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
@ -63,5 +64,24 @@ public class Feed {
values.put(DatabaseConstants.FEED_UPDATED_SECONDS, lastUpdated);
return values;
}
public static Feed fromCursor(Cursor childCursor) {
Feed feed = new Feed();
childCursor.moveToFirst();
feed.active = Boolean.parseBoolean(childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_ACTIVE)));
feed.address = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_ADDRESS));
feed.favicon = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_FAVICON));
feed.faviconColour = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_FAVICON_COLOUR));
feed.faviconFade = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_FAVICON_FADE));
feed.feedId = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_ID));
feed.feedLink = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_LINK));
feed.lastUpdated = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_UPDATED_SECONDS));
feed.negativeCount = childCursor.getInt(childCursor.getColumnIndex(DatabaseConstants.FEED_NEGATIVE_COUNT));
feed.neutralCount = childCursor.getInt(childCursor.getColumnIndex(DatabaseConstants.FEED_NEUTRAL_COUNT));
feed.positiveCount = childCursor.getInt(childCursor.getColumnIndex(DatabaseConstants.FEED_POSITIVE_COUNT));
feed.subscribers = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_SUBSCRIBERS));
feed.title = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_TITLE));
return feed;
}
}

View file

@ -1,6 +1,7 @@
package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
import com.google.gson.annotations.SerializedName;
@ -9,6 +10,8 @@ import com.newsblur.database.DatabaseConstants;
public class Story {
public String id;
@SerializedName("story_permalink")
public String permalink;
@SerializedName("share_count")
@ -18,7 +21,7 @@ public class Story {
public Integer commentCount;
@SerializedName("read_status")
public Boolean read;
public int read;
@SerializedName("story_tags")
public String[] tags;
@ -43,7 +46,7 @@ public class Story {
@SerializedName("intelligence_feed")
public String intelligenceFeed;
@SerializedName("intelligence_authors")
public String intelligenceAuthors;
@ -66,5 +69,21 @@ public class Story {
values.put(DatabaseConstants.STORY_FEED_ID, feedId);
return values;
}
public static Story fromCursor(final Cursor cursor) {
Story story = new Story();
story.authors = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_AUTHORS));
story.content = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_CONTENT));
story.title = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_TITLE));
story.date = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_DATE));
story.permalink = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_PERMALINK));
story.intelligenceAuthors = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_AUTHORS));
story.tags = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_TAGS)), ",");
story.intelligenceFeed = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_INTELLIGENCE_FEED));
story.read = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.STORY_READ));
story.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_FEED_ID));
story.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_ID));
return story;
}
}

View file

@ -1,6 +1,7 @@
package com.newsblur.fragment;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -9,17 +10,20 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import com.newsblur.R;
import com.newsblur.activity.Reading;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.FolderTreeAdapter;
import com.newsblur.domain.Feed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.UIUtils;
import com.newsblur.view.FolderTreeViewBinder;
public class FolderFeedListFragment extends Fragment implements OnGroupClickListener {
public class FolderFeedListFragment extends Fragment implements OnGroupClickListener, OnChildClickListener {
private ExpandableListView list;
private ContentResolver resolver;
@ -58,6 +62,7 @@ public class FolderFeedListFragment extends Fragment implements OnGroupClickList
list.setChildDivider(getActivity().getResources().getDrawable(R.drawable.divider_light));
list.setAdapter(folderAdapter);
list.setOnGroupClickListener(this);
list.setOnChildClickListener(this);
return v;
}
@ -94,4 +99,14 @@ public class FolderFeedListFragment extends Fragment implements OnGroupClickList
return false;
}
@Override
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
final Intent intent = new Intent(getActivity(), Reading.class);
Cursor childCursor = folderAdapter.getChild(groupPosition, childPosition);
String feedId = childCursor.getString(childCursor.getColumnIndex(DatabaseConstants.FEED_ID));
intent.putExtra(Reading.EXTRA_FEED, feedId);
getActivity().startActivity(intent);
return true;
}
}

View file

@ -162,6 +162,7 @@ public class LoginFragment extends Fragment implements OnClickListener, Receiver
Log.d(TAG, "Synchronisation finished.");
final Intent intent = new Intent(Intent.ACTION_SYNC, null, getActivity(), SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_FOLDER_UPDATE);
getActivity().startService(intent);
} else {
if (viewSwitcher != null) {

View file

@ -0,0 +1,46 @@
package com.newsblur.fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import com.newsblur.R;
import com.newsblur.domain.Story;
public class ReadingItemFragment extends Fragment {
final Story story;
public ReadingItemFragment(final Story story) {
this.story = story;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_readingitem, null);
WebView web = (WebView) view.findViewById(R.id.reading_webview);
web.getSettings().setLoadWithOverviewMode(true);
web.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
web.getSettings().setJavaScriptEnabled(true);
web.getSettings().setDomStorageEnabled(true);
web.getSettings().setSupportZoom(true);
web.getSettings().setAppCacheMaxSize(1024*1024*8);
web.getSettings().setAppCachePath("/data/data/com.newsblur/cache");
web.getSettings().setAllowFileAccess(true);
web.getSettings().setAppCacheEnabled(true);
web.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
StringBuilder builder = new StringBuilder();
// TODO: Define a better strategy for rescaling the HTML across device screen sizes and storying this HTML as boilderplate somewhere
builder.append("<html><head><meta name=\"viewport\" content=\"target-densitydpi=device-dpi\" /><link rel=\"stylesheet\" type=\"text/css\" href=\"reading.css\" /></head><body>");
builder.append(story.content);
builder.append("</body></html>");
web.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "UTF-8", null);
return view;
}
}

View file

@ -8,6 +8,7 @@ import org.apache.http.HttpStatus;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.google.gson.Gson;
@ -16,6 +17,7 @@ import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Feed;
import com.newsblur.domain.FolderStructure;
import com.newsblur.domain.Story;
import com.newsblur.network.domain.FeedFolderResponse;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.ProfileResponse;
@ -80,14 +82,22 @@ public class APIManager {
}
}
public void getStoriesForFeed(String feedId) {
public StoriesResponse getStoriesForFeed(String feedId) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDS, feedId);
client.get(APIConstants.URL_FEEDS_STORIES, values);
final APIResponse response = client.get(APIConstants.URL_FEEDS_STORIES, values);
StoriesResponse storiesResponse = gson.fromJson(response.responseString, StoriesResponse.class);
Log.d(TAG, "Response: " + response.responseString);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
Uri feedUri = FeedProvider.STORIES_URI.buildUpon().appendPath(feedId).build();
for (Story story : storiesResponse.stories) {
contentResolver.insert(feedUri, story.getValues());
}
return storiesResponse;
} else {
return null;
}
}
public void getFolderFeedMapping() {

View file

@ -1,16 +1,15 @@
package com.newsblur.service;
import java.util.ArrayList;
import java.util.List;
import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import com.newsblur.network.APIClient;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.StoriesResponse;
/**
* The SyncService is based on an app architecture that tries to place network calls
@ -24,12 +23,19 @@ public class SyncService extends IntentService {
private static final String TAG = "SyncService";
public static final String EXTRA_STATUS_RECEIVER = "resultReceiverExtra";
public static final String EXTRA_TASK_FEED_ID = "taskFeedId";
public final static int STATUS_RUNNING = 0;
public final static int STATUS_FINISHED = 1;
public final static int STATUS_ERROR = 2;
public static final int NOT_RUNNING = -1;
public static final int EXTRA_TASK_FOLDER_UPDATE = 30;
public static final int EXTRA_TASK_FEED_UPDATE = 31;
public APIClient apiClient;
private APIManager apiManager;
public static final String SYNCSERVICE_TASK = "syncservice_task";
public SyncService() {
super(TAG);
@ -45,12 +51,27 @@ public class SyncService extends IntentService {
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "Received SyncService handleIntent call.");
final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER);
try {
try {
if (receiver != null) {
receiver.send(STATUS_RUNNING, Bundle.EMPTY);
}
apiManager.getFolderFeedMapping();
switch (intent.getIntExtra(SYNCSERVICE_TASK , -1)) {
case EXTRA_TASK_FOLDER_UPDATE:
apiManager.getFolderFeedMapping();
break;
case EXTRA_TASK_FEED_UPDATE:
if (!TextUtils.isEmpty(intent.getStringExtra(EXTRA_TASK_FEED_ID))) {
apiManager.getStoriesForFeed(intent.getStringExtra(EXTRA_TASK_FEED_ID));
} else {
Log.e(TAG, "No feed to refresh included in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
default:
Log.e(TAG, "SyncService called without relevant task assignment");
break;
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Couldn't synchronise with Newsblur servers: " + e.getMessage(), e.getCause());
@ -63,6 +84,8 @@ public class SyncService extends IntentService {
if (receiver != null) {
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
Log.e(TAG, "No receiver attached to Sync?");
}
}

View file

@ -63,18 +63,19 @@ public class ActivitiesAdapter extends ArrayAdapter<ActivitiesResponse> {
final ActivitiesResponse activity = getItem(position);
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
ClickableSpan usernameClick = new ClickableSpan() {
@Override
public void onClick(View widget) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, activity.id);
context.startActivity(i);
}
};
if (TextUtils.equals(activity.category, "follow")) {
stringBuilder.append(startedFollowing);
stringBuilder.append(" ");
stringBuilder.append(activity.user.username);
ClickableSpan usernameClick = new ClickableSpan() {
@Override
public void onClick(View widget) {
Intent i = new Intent(context, Profile.class);
i.putExtra(Profile.USER_ID, activity.id);
context.startActivity(i);
}
};
stringBuilder.setSpan(darkgray, 0, startedFollowing.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.setSpan(usernameClick, startedFollowing.length() + 1, startedFollowing.length() + 1 + activity.user.username.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.setSpan(highlight, startedFollowing.length() + 1, startedFollowing.length() + 1 + activity.user.username.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -86,6 +87,7 @@ public class ActivitiesAdapter extends ArrayAdapter<ActivitiesResponse> {
stringBuilder.append(activity.content);
stringBuilder.append("\"");
stringBuilder.setSpan(darkgray, 0, repliedTo.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.setSpan(usernameClick, repliedTo.length() + 1, repliedTo.length() + 1 + activity.user.username.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.setSpan(highlight, repliedTo.length() + 1, repliedTo.length() + 1 + activity.user.username.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
stringBuilder.setSpan(midgray, stringBuilder.length() - activity.content.length() - 2, stringBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (TextUtils.equals(activity.category, "sharedstory")) {

View file

@ -18,7 +18,6 @@ public class FolderTreeViewBinder implements ViewBinder {
private int currentState = AppConstants.STATE_ALL;
// TODO: Make this more efficient
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {