aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rwxr-xr-xapp/src/main/java/org/rssin/android/ArticleActivity.java4
-rwxr-xr-xapp/src/main/java/org/rssin/android/NavigationDrawerAdapter.java59
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/FeedSorter.java30
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/MultiNeuralNetwork.java21
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/MultiNeuralNetworkPrediction.java11
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/NeuralNetwork.java34
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/NeuralNetworkPrediction.java14
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/Neuron.java18
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/PredictionInterface.java14
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/SentenceSplitter.java3
-rwxr-xr-xapp/src/main/java/org/rssin/neurons/TrainingCase.java21
-rwxr-xr-xapp/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java12
-rwxr-xr-xapp/src/main/java/org/rssin/rssin/SortedFeedItemContainer.java9
-rwxr-xr-xapp/src/main/res/values/strings.xml5
14 files changed, 184 insertions, 71 deletions
diff --git a/app/src/main/java/org/rssin/android/ArticleActivity.java b/app/src/main/java/org/rssin/android/ArticleActivity.java
index f5c0631..2512949 100755
--- a/app/src/main/java/org/rssin/android/ArticleActivity.java
+++ b/app/src/main/java/org/rssin/android/ArticleActivity.java
@@ -77,12 +77,12 @@ public class ArticleActivity extends ActionBarActivity {
//noinspection SimplifiableIfStatement
if (id == R.id.article_action_dislike) {
- Toast.makeText(this, "Feedback saved.", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, getString(R.string.article_feedback_saved), Toast.LENGTH_SHORT).show();
container.feedback(this, Dislike);
return true;
} else if(id == R.id.article_action_like)
{
- Toast.makeText(this, "Feedback saved.", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, getString(R.string.article_feedback_saved), Toast.LENGTH_SHORT).show();
container.feedback(this, Like);
return true;
} else if(id == R.id.article_action_share)
diff --git a/app/src/main/java/org/rssin/android/NavigationDrawerAdapter.java b/app/src/main/java/org/rssin/android/NavigationDrawerAdapter.java
index 90ce041..2a24342 100755
--- a/app/src/main/java/org/rssin/android/NavigationDrawerAdapter.java
+++ b/app/src/main/java/org/rssin/android/NavigationDrawerAdapter.java
@@ -33,23 +33,22 @@ class NavigationDrawerAdapter implements ListAdapter {
public void updateMenu(Context context) throws IOException {
menuItems.clear();
- menuItems.add(new MenuSection("START"));
- menuItems.add(new MenuUnifiedInbox());
- menuItems.add(new ManageFiltersSection("FILTERS (EDIT)"));
+ menuItems.add(new MenuSection(context.getString(R.string.navigation_drawer_start)));
+ menuItems.add(new MenuUnifiedInbox(context));
+ menuItems.add(new ManageFiltersSection(context.getString(R.string.navigation_drawer_edit_filters)));
for(Filter f : FiltersList.getInstance(context).getFilters())
{
menuItems.add(new MenuFilterItem(f));
}
- menuItems.add(new ManageFeedsSection("FEEDS (EDIT)"));
+ menuItems.add(new ManageFeedsSection(context.getString(R.string.navigation_drawer_edit_feeds)));
for(Feed f : FeedsList.getInstance(context).getFeeds())
{
menuItems.add(new MenuFeedItem(f));
}
-
for(DataSetObserver observer : observers)
{
observer.onChanged();
@@ -181,57 +180,11 @@ class NavigationDrawerAdapter implements ListAdapter {
}
}
- class FilterListMenuItem extends MenuItem
- {
- public FilterListMenuItem()
- {
- super("Manage filters...");
- }
-
- @Override
- public int getItemViewType() {
- return ITEM_VIEW_TYPE_NORMAL;
- }
-
- @Override
- public Fragment getFragment() {
- return NavigationDrawerManageFiltersFragment.newInstance();
- }
-
- @Override
- public int getPreferredMenuId() {
- return R.menu.menu_filters;
- }
- }
-
- class FeedListMenuItem extends MenuItem
- {
- public FeedListMenuItem()
- {
- super("Manage feeds...");
- }
-
- @Override
- public int getItemViewType() {
- return ITEM_VIEW_TYPE_NORMAL;
- }
-
- @Override
- public Fragment getFragment() {
- return NavigationDrawerManageFeedsFragment.newInstance();
- }
-
- @Override
- public int getPreferredMenuId() {
- return R.menu.menu_feeds;
- }
- }
-
class MenuUnifiedInbox extends MenuItem
{
- public MenuUnifiedInbox()
+ public MenuUnifiedInbox(Context context)
{
- super("All feeds");
+ super(context.getString(R.string.navigation_drawer_all_feeds));
}
@Override
diff --git a/app/src/main/java/org/rssin/neurons/FeedSorter.java b/app/src/main/java/org/rssin/neurons/FeedSorter.java
index 0af57ae..4cd5856 100755
--- a/app/src/main/java/org/rssin/neurons/FeedSorter.java
+++ b/app/src/main/java/org/rssin/neurons/FeedSorter.java
@@ -19,6 +19,7 @@ import java.util.TimeZone;
/**
* @author Jos.
+ * Sorts lists of feeds based on the output of a neural network, using date/time + title + author + category as inputs.
*/
public class FeedSorter implements Storable {
private static final long serialVersionUID = 0;
@@ -64,6 +65,9 @@ public class FeedSorter implements Storable {
authorInputs = (Hashtable<String, Integer>) stream.readObject();
}
+ /**
+ * Initializes a new FeedSorter, and creates the basic inputs for the Neural network.4
+ */
public FeedSorter() {
biasInput = nn.addInput();
for (int i = 0; i < 12; i++) {
@@ -80,6 +84,11 @@ public class FeedSorter implements Storable {
isNight = nn.addInput();
}
+ /**
+ * Returns a prediction for the provided FeedItem.
+ * @param item The item to predict the interest of the user of.
+ * @return The prediction.
+ */
private PredictionInterface getPrediction(FeedItem item) {
List<String> words = splitter.splitSentence(item.getTitle());
@@ -123,18 +132,31 @@ public class FeedSorter implements Storable {
return nn.computeOutput(inputs);
}
+ /**
+ * @return a new array, the same size as the number of inputs, initialized to -1.
+ */
private double[] newArrayInitializedToNegativeOne() {
double[] inputs = new double[nn.getInputCount()];
Arrays.fill(inputs, 0, inputs.length, -1);
return inputs;
}
+ /**
+ * Adds new inputs to the Neural Network
+ * @param words The string identifiers of the inputs.
+ * @param map The map of string identifiers to input IDs. This map is modified.
+ */
private void addNewInputs(Iterable<String> words, Hashtable<String, Integer> map) {
for (String word : words) {
addNewInput(word, map);
}
}
+ /**
+ * Adds a single new input to the neural network.
+ * @param word The string identifier.
+ * @param map The map op string identifiers to input IDs. This map is modified.
+ */
private void addNewInput(String word, Hashtable<String, Integer> map) {
if (word != null) {
word = word.toLowerCase();
@@ -151,17 +173,21 @@ public class FeedSorter implements Storable {
* @param feedback The feedback. Like will move these types of items up in the list,
* dislike will move them down.
*/
- public void feedback(FeedItem item, Feedback feedback) {
+ public TrainingCase feedback(FeedItem item, Feedback feedback) {
+ TrainingCase trainingCase;
Log.d("FeedSorter", "Collected feedback: " + feedback.toString());
PredictionInterface prediction = getPrediction(item);
prediction.learn(feedback.toExpectedOutput());
- trainingCases.add(new TrainingCase(prediction.getInputs(), feedback));
+ trainingCase = new TrainingCase(prediction.getInputs(), feedback);
+ trainingCases.add(trainingCase);
final int MAX_TRAINING_HISTORY = 250;
while (trainingCases.size() > MAX_TRAINING_HISTORY) {
trainingCases.remove(0);
}
+
+ return trainingCase;
}
/**
diff --git a/app/src/main/java/org/rssin/neurons/MultiNeuralNetwork.java b/app/src/main/java/org/rssin/neurons/MultiNeuralNetwork.java
index 9ba40e0..f7a7099 100755
--- a/app/src/main/java/org/rssin/neurons/MultiNeuralNetwork.java
+++ b/app/src/main/java/org/rssin/neurons/MultiNeuralNetwork.java
@@ -21,6 +21,11 @@ class MultiNeuralNetwork implements Serializable {
networks = SerializationTools.readArray(stream, NeuralNetwork.class);
}
+ /**
+ * Creates a new instance of the MultiNeuralNetwork
+ * @param numNetworks The number of underlying networks.
+ * @param numHiddenNodes The number of hidden nodes each network has.
+ */
public MultiNeuralNetwork(int numNetworks, int numHiddenNodes) {
networks = new NeuralNetwork[numNetworks];
for (int i = 0; i < networks.length; i++) {
@@ -28,15 +33,26 @@ class MultiNeuralNetwork implements Serializable {
}
}
+ /**
+ * Calls addInput() on all NeuralNetworks.
+ * @return the new input ID.
+ */
public int addInput() {
int id = 0;
for (NeuralNetwork network : networks) {
id = network.addInput();
}
+ // Unless a neural network's addInput() is called individually, which should be impossible
+ // because the list of networks is private, all IDs returned by network.addInput() are the same.
return id;
}
+ /**
+ * Returns a prediction from the Neural Networks based on the inputs.
+ * @param inputs The list of double inputs.
+ * @return The prediction.
+ */
public PredictionInterface computeOutput(double[] inputs) {
PredictionInterface[] predictions = new PredictionInterface[networks.length];
for (int i = 0; i < predictions.length; i++) {
@@ -46,7 +62,12 @@ class MultiNeuralNetwork implements Serializable {
return new MultiNeuralNetworkPrediction(predictions);
}
+ /**
+ * Returns the number of inputs in the neural network.
+ * @return
+ */
public int getInputCount() {
+ // All neural networks will have the same number of inputs. See addInput().
return networks[0].getInputCount();
}
}
diff --git a/app/src/main/java/org/rssin/neurons/MultiNeuralNetworkPrediction.java b/app/src/main/java/org/rssin/neurons/MultiNeuralNetworkPrediction.java
index f618749..c03cab3 100755
--- a/app/src/main/java/org/rssin/neurons/MultiNeuralNetworkPrediction.java
+++ b/app/src/main/java/org/rssin/neurons/MultiNeuralNetworkPrediction.java
@@ -2,6 +2,7 @@ package org.rssin.neurons;
/**
* @author Jos.
+ * A prediction that bases its result on the average of multiple other predictions.
*/
class MultiNeuralNetworkPrediction implements PredictionInterface {
private final PredictionInterface[] predictions;
@@ -14,6 +15,9 @@ class MultiNeuralNetworkPrediction implements PredictionInterface {
this.predictions = predictions;
}
+ /**
+ * @return the prediction, as a value in [-1, 1].
+ */
public double getOutput() {
double average = 0;
for (PredictionInterface prediction : predictions) {
@@ -23,12 +27,19 @@ class MultiNeuralNetworkPrediction implements PredictionInterface {
return average / (double) predictions.length;
}
+ /**
+ * Provides the neural networks with feedback.
+ * @param expectedOutput The expected output for the input values (getInputs()).
+ */
public void learn(double expectedOutput) {
for (PredictionInterface prediction : predictions) {
prediction.learn(expectedOutput);
}
}
+ /**
+ * @return The inputs provided to the neural network.
+ */
public double[] getInputs() {
return predictions[0].getInputs();
}
diff --git a/app/src/main/java/org/rssin/neurons/NeuralNetwork.java b/app/src/main/java/org/rssin/neurons/NeuralNetwork.java
index 972f9de..2d35247 100755
--- a/app/src/main/java/org/rssin/neurons/NeuralNetwork.java
+++ b/app/src/main/java/org/rssin/neurons/NeuralNetwork.java
@@ -9,6 +9,7 @@ import java.io.Serializable;
/**
* @author Jos.
+ * A 'simple' implementation of a neural network with one hidden layer.
*/
class NeuralNetwork implements Serializable {
private static final long serialVersionUID = 0;
@@ -25,6 +26,10 @@ class NeuralNetwork implements Serializable {
outputNode = (Neuron) stream.readObject();
}
+ /**
+ * Creates a new instance of the neural network.
+ * @param numHiddenNodes The number of hidden nodes in the neural network.
+ */
NeuralNetwork(int numHiddenNodes) {
if (numHiddenNodes < 1) {
throw new IllegalArgumentException("numHiddenNodes must be > 0");
@@ -39,6 +44,9 @@ class NeuralNetwork implements Serializable {
outputNode = new Neuron(numHiddenNodes + 1);
}
+ /**
+ * @return the new input ID.
+ */
@SuppressLint("Assert")
int addInput() {
assert hiddenNodes.length > 0;
@@ -51,6 +59,11 @@ class NeuralNetwork implements Serializable {
return result;
}
+ /**
+ * Calculcates the output of the neural network for the given input values.
+ * @param inputs The inputs for the neural network.
+ * @return The output of the neural network.
+ */
PredictionInterface computeOutput(double[] inputs) {
double[] intermediateValues = new double[outputNode.getWeightCount()];
@@ -74,8 +87,12 @@ class NeuralNetwork implements Serializable {
return new NeuralNetworkPrediction(this, inputs, intermediateValues, HyperTan(result));
}
+ /**
+ * Train the neural network
+ * @param p The original prediction
+ * @param expectedOutput The expected output of the prediction.
+ */
void learn(NeuralNetworkPrediction p, double expectedOutput) {
- //TODO: See if adding momentum helps avoid local minimum
double actualOutput = p.getOutput();
double[] intermediateValues = p.getIntermediateValues();
double[] inputs = p.getInputs();
@@ -97,6 +114,13 @@ class NeuralNetwork implements Serializable {
updateWeights(intermediateValues, inputs, hiddenGradients, outputGradient);
}
+ /**
+ * Method to update the weights of the nodes.
+ * @param intermediateValues The intermediate values generated by the hidden nodes
+ * @param inputs The input values
+ * @param hiddenGradients The hidden gradients, calculated in learn().
+ * @param outputGradient The output gradients, calculated in learn().
+ */
private void updateWeights(double[] intermediateValues, double[] inputs, double[] hiddenGradients, double outputGradient) {
final double learningRate = 0.2;
@@ -119,12 +143,20 @@ class NeuralNetwork implements Serializable {
}
}
+ /**
+ * HyperTan that returns -1 or 1 when the value is smaller than -10 or bigger than 10, respectively.
+ * @param x The input
+ * @return The result of HyperTan.
+ */
private static double HyperTan(double x) {
if (x < -10.0) return -1.0;
else if (x > 10.0) return 1.0;
else return Math.tanh(x);
}
+ /**
+ * @return The number of inputs for this neural network.
+ */
int getInputCount() {
return hiddenNodes[0].getWeightCount();
}
diff --git a/app/src/main/java/org/rssin/neurons/NeuralNetworkPrediction.java b/app/src/main/java/org/rssin/neurons/NeuralNetworkPrediction.java
index 169caee..40dca02 100755
--- a/app/src/main/java/org/rssin/neurons/NeuralNetworkPrediction.java
+++ b/app/src/main/java/org/rssin/neurons/NeuralNetworkPrediction.java
@@ -2,6 +2,7 @@ package org.rssin.neurons;
/**
* @author Jos.
+ * A prediction made by the neural network.
*/
class NeuralNetworkPrediction implements PredictionInterface {
private final double[] inputs;
@@ -16,18 +17,31 @@ class NeuralNetworkPrediction implements PredictionInterface {
this.nn = nn;
}
+ /**
+ * @return The inputs that resulted in this prediction.
+ */
public double[] getInputs() {
return inputs;
}
+ /**
+ * @return The intermediate values that resulted in the output, as calculated by NeuralNetwork.computeOutput.
+ */
public double[] getIntermediateValues() {
return intermediateValues;
}
+ /**
+ * @return The output of the neural network.
+ */
public double getOutput() {
return output;
}
+ /**
+ * Provides feedback to the neural network.
+ * @param expectedOutput The expected output for the input values.
+ */
public void learn(double expectedOutput) {
nn.learn(this, expectedOutput);
}
diff --git a/app/src/main/java/org/rssin/neurons/Neuron.java b/app/src/main/java/org/rssin/neurons/Neuron.java
index da0e4ca..52fcdba 100755
--- a/app/src/main/java/org/rssin/neurons/Neuron.java
+++ b/app/src/main/java/org/rssin/neurons/Neuron.java
@@ -10,6 +10,7 @@ import java.util.Random;
/**
* @author Jos.
+ * A class that contains the weights for a single neuron in the neural network.
*/
class Neuron implements Serializable {
private static final long serialVersionUID = 0;
@@ -35,6 +36,10 @@ class Neuron implements Serializable {
}
}
+ /**
+ * Adds a weight to the neuron.
+ * @return The index of the weight.
+ */
public int addWeight() {
// Initial values range from -.5 to .5. The exact value does not matter,
// as long as they aren't all 0.
@@ -42,14 +47,27 @@ class Neuron implements Serializable {
return weights.size() - 1;
}
+ /**
+ * Returns the value of a weight
+ * @param i The weight index
+ * @return The value of the weight
+ */
public double getWeight(int i) {
return weights.get(i);
}
+ /**
+ * Modifies a weight, by adding delta to its value
+ * @param i The weight index
+ * @param delta The amount the value will change.
+ */
public void adjustWeight(int i, double delta) {
weights.set(i, weights.get(i) + delta);
}
+ /**
+ * @return the number of weights in this neuron.
+ */
public int getWeightCount() {
return weights.size();
}
diff --git a/app/src/main/java/org/rssin/neurons/PredictionInterface.java b/app/src/main/java/org/rssin/neurons/PredictionInterface.java
index ff46992..891707a 100755
--- a/app/src/main/java/org/rssin/neurons/PredictionInterface.java
+++ b/app/src/main/java/org/rssin/neurons/PredictionInterface.java
@@ -2,11 +2,23 @@ package org.rssin.neurons;
/**
* @author Jos.
+ * Interface for predictions generated by either NeuralNetwork or MultiNeuralNetwork.
*/
-interface PredictionInterface {
+interface PredictionInterface
+{
+ /**
+ * @return The output of the neural network.
+ */
double getOutput();
+ /**
+ * Provides feedback to the neural network.
+ * @param expectedOutput The expected output for this prediction.
+ */
void learn(double expectedOutput);
+ /**
+ * @return The inputs that were given to the neural network.
+ */
double[] getInputs();
}
diff --git a/app/src/main/java/org/rssin/neurons/SentenceSplitter.java b/app/src/main/java/org/rssin/neurons/SentenceSplitter.java
index 29e34bc..002071e 100755
--- a/app/src/main/java/org/rssin/neurons/SentenceSplitter.java
+++ b/app/src/main/java/org/rssin/neurons/SentenceSplitter.java
@@ -12,7 +12,8 @@ import java.util.regex.Pattern;
public class SentenceSplitter implements Serializable {
private static final long serialVersionUID = 0;
- private final Pattern wordMatch = Pattern.compile("[\\w-]+");//For unicode support, add the Pattern.UNICODE_CHARACTER_CLASS flag. Works only in Java 7+.
+ //For unicode support, add the Pattern.UNICODE_CHARACTER_CLASS flag. Works only in Java 7+, currently not supported on Android.
+ private final Pattern wordMatch = Pattern.compile("[\\w-]+");
public SentenceSplitter() {
}
diff --git a/app/src/main/java/org/rssin/neurons/TrainingCase.java b/app/src/main/java/org/rssin/neurons/TrainingCase.java
index 69f72cb..d1fee3a 100755
--- a/app/src/main/java/org/rssin/neurons/TrainingCase.java
+++ b/app/src/main/java/org/rssin/neurons/TrainingCase.java
@@ -4,22 +4,37 @@ import java.io.Serializable;
/**
* @author Jos.
+ * A training case for the FeedSorter.
*/
-class TrainingCase implements Serializable {
+public class TrainingCase implements Serializable {
private static final long serialVersionUID = 0;
private final double[] inputs;
- private final Feedback feedback;
+ private Feedback feedback;
public TrainingCase(double[] inputs, Feedback feedback) {
this.inputs = inputs;
this.feedback = feedback;
}
- public double[] getInputs() {
+ /**
+ * @return The inputs that were given to the neural network.
+ */
+ double[] getInputs() {
return inputs;
}
+ /**
+ * @return The expected prediction
+ */
public Feedback getFeedback() {
return feedback;
}
+
+ /**
+ * @param feedback The new expected prediction.
+ */
+ public void setFeedback(Feedback feedback)
+ {
+ this.feedback = feedback;
+ }
}
diff --git a/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java b/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java
index a317a27..524ac46 100755
--- a/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java
+++ b/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java
@@ -16,11 +16,15 @@ import java.util.List;
/**
* Created by Jos on 20-5-2015.
- * @todo javadoc
+ * A class that loads & sorts FeedItems from a list of Feeds.
*/
public class FeedLoaderAndSorter {
private final List<Feed> feeds;
+ /**
+ * Creates a new FeedLoaderAndSorter that can load FeedItems the list of feeds.
+ * @param feeds The list of Feeds to load FeedItems from.
+ */
public FeedLoaderAndSorter(List<Feed> feeds) {
this.feeds = feeds;
}
@@ -81,10 +85,4 @@ public class FeedLoaderAndSorter {
return count == 0;
}
}
-
- private static boolean contains(String haystack, String needle) {
- return haystack != null
- && needle != null
- && haystack.toLowerCase().contains(needle.toLowerCase());
- }
}
diff --git a/app/src/main/java/org/rssin/rssin/SortedFeedItemContainer.java b/app/src/main/java/org/rssin/rssin/SortedFeedItemContainer.java
index 2a69bfa..f96382a 100755
--- a/app/src/main/java/org/rssin/rssin/SortedFeedItemContainer.java
+++ b/app/src/main/java/org/rssin/rssin/SortedFeedItemContainer.java
@@ -6,6 +6,7 @@ import org.rssin.android.FeedSorterProvider;
import org.rssin.android.FiltersList;
import org.rssin.neurons.FeedSorter;
import org.rssin.neurons.Feedback;
+import org.rssin.neurons.TrainingCase;
import org.rssin.rss.FeedItem;
import java.io.Serializable;
@@ -16,6 +17,7 @@ import java.io.Serializable;
public class SortedFeedItemContainer implements Comparable<SortedFeedItemContainer>, Serializable {
private FeedItem feeditem;
private long score;
+ private TrainingCase trainingCase;
public SortedFeedItemContainer(FeedItem feeditem)
{
@@ -38,7 +40,12 @@ public class SortedFeedItemContainer implements Comparable<SortedFeedItemContain
public void feedback(Context context, Feedback feedback)
{
- FeedSorterProvider.getInstance(context).getFeedSorter().feedback(getFeeditem(), feedback);
+ if(trainingCase == null)
+ {
+ trainingCase = FeedSorterProvider.getInstance(context).getFeedSorter().feedback(getFeeditem(), feedback);
+ }else {
+ trainingCase.setFeedback(feedback);
+ }
}
public void setScore(long score) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1a279c7..3b3a884 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -65,4 +65,9 @@
<string name="article_author_unknown">Unknown author</string>
<string name="article_published_on">Published on:</string>
<string name="article_published_on_unknown"/>
+ <string name="navigation_drawer_all_feeds">All feeds</string>
+ <string name="navigation_drawer_start">START</string>
+ <string name="navigation_drawer_edit_filters">FILTERS (EDIT)</string>
+ <string name="navigation_drawer_edit_feeds">FEEDS (EDIT)</string>
+ <string name="article_feedback_saved">Feedback saved.</string>
</resources>