Added ability to set intelligence for tags from feed-reading view.
Before Width: | Height: | Size: 397 B After Width: | Height: | Size: 492 B |
BIN
media/android/NewsBlur/res/drawable-hdpi/tag_negative.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
media/android/NewsBlur/res/drawable-hdpi/tag_positive.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 436 B |
BIN
media/android/NewsBlur/res/drawable-ldpi/tag_negative.png
Normal file
After Width: | Height: | Size: 720 B |
BIN
media/android/NewsBlur/res/drawable-ldpi/tag_positive.png
Normal file
After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 478 B |
BIN
media/android/NewsBlur/res/drawable-mdpi/tag_negative.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 933 B |
BIN
media/android/NewsBlur/res/drawable-mdpi/tag_positive.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 927 B |
BIN
media/android/NewsBlur/res/drawable-xhdpi/classify_negative.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
media/android/NewsBlur/res/drawable-xhdpi/classify_positive.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 50 KiB |
|
@ -0,0 +1,33 @@
|
|||
<?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" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tag_negative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:src="@drawable/classify_negative" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tag_positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:src="@drawable/classify_positive" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_toLeftOf="@id/tag_positive"
|
||||
android:layout_toRightOf="@id/tag_negative"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="10dp"
|
||||
android:textSize="20dp" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -19,6 +19,8 @@
|
|||
<string name="title_feed_search">Search for feeds</string>
|
||||
<string name="add_feed_message">Add \"%s\" to your feeds?</string>
|
||||
|
||||
<string name="train_on">Train on \"%s\"</string>
|
||||
|
||||
<string name="dialog_title">Alert</string>
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public class FeedReading extends Reading {
|
|||
feed = Feed.fromCursor(feedCursor);
|
||||
setTitle(feed.title);
|
||||
|
||||
readingAdapter = new FeedReadingAdapter(getSupportFragmentManager(), feed, stories);
|
||||
readingAdapter = new FeedReadingAdapter(getSupportFragmentManager(), feed, stories, classifier);
|
||||
|
||||
setupPager();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.support.v4.app.Fragment;
|
|||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
import com.newsblur.activity.ReadingAdapter;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.fragment.LoadingFragment;
|
||||
|
@ -13,10 +14,12 @@ import com.newsblur.fragment.ReadingItemFragment;
|
|||
public class FeedReadingAdapter extends ReadingAdapter {
|
||||
|
||||
private final Feed feed;
|
||||
private Classifier classifier;
|
||||
|
||||
public FeedReadingAdapter(FragmentManager fm, Feed feed, Cursor stories) {
|
||||
public FeedReadingAdapter(FragmentManager fm, Feed feed, Cursor stories, Classifier classifier) {
|
||||
super(fm, stories);
|
||||
this.feed = feed;
|
||||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +29,7 @@ public class FeedReadingAdapter extends ReadingAdapter {
|
|||
return loadingFragment;
|
||||
} else {
|
||||
stories.moveToPosition(position);
|
||||
return ReadingItemFragment.newInstance(Story.fromCursor(stories), feed.faviconColour, feed.faviconFade);
|
||||
return ReadingItemFragment.newInstance(Story.fromCursor(stories), feed.faviconColour, feed.faviconFade, classifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ public class MixedExpandableListAdapter extends BaseExpandableListAdapter{
|
|||
@Override
|
||||
public Cursor getGroup(int groupPosition) {
|
||||
if (groupPosition >= blogCursorHelper.getCount()) {
|
||||
return folderCursorHelper.moveTo(groupPosition - blogCursorHelper.getCount());
|
||||
return folderCursorHelper.moveTo(groupPosition - blogCursorHelper.getCount() - 1);
|
||||
} else {
|
||||
return blogCursorHelper.moveTo(groupPosition);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ public class MixedFeedsReadingAdapter extends ReadingAdapter {
|
|||
stories.moveToPosition(position);
|
||||
String feedFaviconColor = stories.getString(stories.getColumnIndex(DatabaseConstants.FEED_FAVICON_COLOUR));
|
||||
String feedFaviconFade = stories.getString(stories.getColumnIndex(DatabaseConstants.FEED_FAVICON_FADE));
|
||||
return ReadingItemFragment.newInstance(Story.fromCursor(stories), feedFaviconColor, feedFaviconFade);
|
||||
return ReadingItemFragment.newInstance(Story.fromCursor(stories), feedFaviconColor, feedFaviconFade, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.newsblur.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -10,9 +11,11 @@ import android.database.Cursor;
|
|||
import com.google.gson.annotations.SerializedName;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
|
||||
public class Classifier {
|
||||
public class Classifier implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8958319817246110753L;
|
||||
public static final int AUTHOR = 0, FEED = 1, TITLE = 2, TAG = 3;
|
||||
public static final int LIKE = 1, DISLIKE = -1, CLEAR_DISLIKE = 3, CLEAR_LIKE = 4;
|
||||
|
||||
@SerializedName("authors")
|
||||
public HashMap<String, Integer> authors = new HashMap<String, Integer>();
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
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.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.network.APIManager;
|
||||
|
||||
public class ClassifierDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String KEY = "key";
|
||||
private static final String FEED_ID = "feed_id";
|
||||
private static final String TYPE = "type";
|
||||
private static final String CLASSIFIER = "classifier";
|
||||
|
||||
private String key, feedId;
|
||||
private Classifier classifier;
|
||||
private int classifierType;
|
||||
|
||||
private APIManager apiManager;
|
||||
|
||||
|
||||
public static ClassifierDialogFragment newInstance(final String feedId, final Classifier classifier, final String key, final int classifierType) {
|
||||
ClassifierDialogFragment frag = new ClassifierDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY, key);
|
||||
args.putString(FEED_ID, feedId);
|
||||
args.putInt(TYPE, classifierType);
|
||||
args.putSerializable(CLASSIFIER, classifier);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.dialog);
|
||||
feedId = getArguments().getString(FEED_ID);
|
||||
key = getArguments().getString(KEY);
|
||||
classifierType = getArguments().getInt(TYPE);
|
||||
classifier = (Classifier) getArguments().getSerializable(CLASSIFIER);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
apiManager = new APIManager(getActivity());
|
||||
View v = inflater.inflate(R.layout.fragment_classify_dialog, container, false);
|
||||
final TextView message = (TextView) v.findViewById(R.id.dialog_message);
|
||||
message.setText(key);
|
||||
|
||||
final ImageView classifyPositive = (ImageView) v.findViewById(R.id.tag_positive);
|
||||
final ImageView classifyNegative = (ImageView) v.findViewById(R.id.tag_negative);
|
||||
|
||||
if (classifier.tags.containsKey(key)) {
|
||||
switch (classifier.tags.get(key)) {
|
||||
case Classifier.LIKE:
|
||||
classifyPositive.setImageResource(R.drawable.tag_positive_already);
|
||||
break;
|
||||
case Classifier.DISLIKE:
|
||||
classifyNegative.setImageResource(R.drawable.tag_negative_already);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
classifyNegative.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... arg0) {
|
||||
if (classifier.tags.containsKey(key) && classifier.tags.get(key) == Classifier.DISLIKE) {
|
||||
return apiManager.trainClassifier(feedId, key, Classifier.TAG, Classifier.CLEAR_DISLIKE);
|
||||
} else {
|
||||
return apiManager.trainClassifier(feedId, key, Classifier.TAG, Classifier.DISLIKE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result.booleanValue()) {
|
||||
Toast.makeText(getActivity(), "Classifier saved", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "Error saving classifier", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
ClassifierDialogFragment.this.dismiss();
|
||||
};
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
classifyPositive.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... arg0) {
|
||||
if (classifier.tags.containsKey(key) && classifier.tags.get(key) == Classifier.LIKE) {
|
||||
return apiManager.trainClassifier(feedId, key, Classifier.TAG, Classifier.CLEAR_LIKE);
|
||||
} else {
|
||||
return apiManager.trainClassifier(feedId, key, Classifier.TAG, Classifier.LIKE);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result.booleanValue()) {
|
||||
Toast.makeText(getActivity(), "Classifier saved", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "Error saving classifier", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
ClassifierDialogFragment.this.dismiss();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
package com.newsblur.fragment;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.GridLayout;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
|
@ -15,6 +20,7 @@ import android.widget.TextView;
|
|||
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.activity.NewsBlurApplication;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.domain.Story;
|
||||
import com.newsblur.network.APIManager;
|
||||
import com.newsblur.network.SetupCommentSectionTask;
|
||||
|
@ -28,16 +34,18 @@ public class ReadingItemFragment extends Fragment {
|
|||
private APIManager apiManager;
|
||||
private ImageLoader imageLoader;
|
||||
private String feedColor;
|
||||
private Classifier classifier;
|
||||
private String feedFade;
|
||||
private ContentResolver resolver;
|
||||
|
||||
public static ReadingItemFragment newInstance(Story story, String feedFaviconColor, String feedFaviconFade) {
|
||||
public static ReadingItemFragment newInstance(Story story, String feedFaviconColor, String feedFaviconFade, Classifier classifier) {
|
||||
ReadingItemFragment readingFragment = new ReadingItemFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable("story", story);
|
||||
args.putString("feedColor", feedFaviconColor);
|
||||
args.putString("feedFade", feedFaviconFade);
|
||||
args.putSerializable("classifier", classifier);
|
||||
readingFragment.setArguments(args);
|
||||
|
||||
return readingFragment;
|
||||
|
@ -56,6 +64,8 @@ public class ReadingItemFragment extends Fragment {
|
|||
|
||||
feedColor = getArguments().getString("feedColor");
|
||||
feedFade = getArguments().getString("feedFade");
|
||||
|
||||
classifier = (Classifier) getArguments().getSerializable("classifier");
|
||||
}
|
||||
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
||||
|
@ -63,7 +73,6 @@ public class ReadingItemFragment extends Fragment {
|
|||
|
||||
View view = inflater.inflate(R.layout.fragment_readingitem, null);
|
||||
|
||||
|
||||
WebView web = (WebView) view.findViewById(R.id.reading_webview);
|
||||
setupWebview(web);
|
||||
setupItemMetadata(view);
|
||||
|
@ -94,7 +103,7 @@ public class ReadingItemFragment extends Fragment {
|
|||
}
|
||||
|
||||
View sidebar = view.findViewById(R.id.row_item_sidebar);
|
||||
|
||||
|
||||
if (story.getIntelligenceTotal() > 0) {
|
||||
sidebar.setBackgroundResource(R.drawable.positive_count_circle);
|
||||
} else if (story.getIntelligenceTotal() == 0) {
|
||||
|
@ -111,16 +120,30 @@ public class ReadingItemFragment extends Fragment {
|
|||
itemTitle.setText(story.title);
|
||||
itemAuthors.setText(story.authors);
|
||||
|
||||
setupTags(view);
|
||||
}
|
||||
|
||||
|
||||
private void setupTags(View view) {
|
||||
GridLayout tagContainer = (GridLayout) view.findViewById(R.id.reading_item_tags);
|
||||
|
||||
if (story.tags != null || story.tags.length > 0) {
|
||||
tagContainer.setVisibility(View.VISIBLE);
|
||||
for (String tag : story.tags) {
|
||||
for (final String tag : story.tags) {
|
||||
View v = inflater.inflate(R.layout.tag_view, null);
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||
params.columnSpec = GridLayout.spec(1, 1);
|
||||
TextView tagText = (TextView) v.findViewById(R.id.tag_text);
|
||||
tagText.setText(tag);
|
||||
|
||||
v.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ClassifierDialogFragment classifierFragment = ClassifierDialogFragment.newInstance(story.feedId, classifier, tag, Classifier.TAG);
|
||||
classifierFragment.show(ReadingItemFragment.this.getFragmentManager(), "dialog");
|
||||
}
|
||||
});
|
||||
|
||||
tagContainer.addView(v);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ public class APIConstants {
|
|||
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 URL_CLASSIFIER_SAVE = "http://www.newsblur.com/classifier/save";
|
||||
|
||||
public static final String PARAMETER_FEEDS = "feeds";
|
||||
public static final String PARAMETER_PASSWORD = "password";
|
||||
public static final String PARAMETER_USER_ID = "user_id";
|
||||
|
@ -50,10 +52,4 @@ public class APIConstants {
|
|||
|
||||
public static final String NEWSBLUR_URL = "http://www.newsblur.com";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.google.gson.reflect.TypeToken;
|
|||
import com.newsblur.R;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.database.FeedProvider;
|
||||
import com.newsblur.domain.Classifier;
|
||||
import com.newsblur.domain.Comment;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.domain.FeedResult;
|
||||
|
@ -333,6 +334,53 @@ public class APIManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean trainClassifier(String feedId, String key, int type, int action) {
|
||||
String typeText = null;
|
||||
String actionText = null;
|
||||
|
||||
switch (type) {
|
||||
case Classifier.AUTHOR:
|
||||
typeText = "author";
|
||||
break;
|
||||
case Classifier.TAG:
|
||||
typeText = "tag";
|
||||
break;
|
||||
case Classifier.TITLE:
|
||||
typeText = "title";
|
||||
break;
|
||||
case Classifier.FEED:
|
||||
typeText = "feed";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case Classifier.CLEAR_LIKE:
|
||||
actionText = "remove_like_";
|
||||
break;
|
||||
case Classifier.CLEAR_DISLIKE:
|
||||
actionText = "remove_dislike_";
|
||||
break;
|
||||
case Classifier.LIKE:
|
||||
actionText = "like_";
|
||||
break;
|
||||
case Classifier.DISLIKE:
|
||||
actionText = "dislike_";
|
||||
break;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();;
|
||||
builder.append(actionText);
|
||||
builder.append(typeText);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(builder.toString(), key);
|
||||
values.put(APIConstants.PARAMETER_FEEDID, feedId);
|
||||
|
||||
final APIClient client = new APIClient(context);
|
||||
final APIResponse response = client.post(APIConstants.URL_CLASSIFIER_SAVE, values);
|
||||
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
|
||||
}
|
||||
|
||||
public ProfileResponse getUser(String userId) {
|
||||
final APIClient client = new APIClient(context);
|
||||
|
|