Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Commit

Permalink
Closes #8: Handle markdown in descriptions (#129)
Browse files Browse the repository at this point in the history
* Convert bold and link markdown to HTML equivalents (handled by EncodingFixer)

* Subreddit mentions (eg "/r/Android") are converted to HTML links

* Support for HTML app descriptions in App Details view and App List view

* Clickable links in app details view

* Unit tests for changes, updates to other tests and mocks
  • Loading branch information
bobheadxi committed Aug 24, 2017
1 parent 3df0117 commit c1b972b
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ public BaseParser(EncodingFixer encodingFixer) {
String fixEncoding(String input) {
return encodingFixer.fixHtmlEscapes(input);
}

String fixMarkdown(String input) {
return encodingFixer.convertMarkdownToHtml(input);
}

String convertSubredditsToLinks(String input) {
return encodingFixer.convertSubredditsToLinks(input);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ public DescriptionColumnParser(EncodingFixer encodingFixer) {
@Override
public void parse(AppInfo appInfo, Map<Column, String> rawColumns) {
final String rawDescriptionString = rawColumns.get(Column.DESCRIPTION);
// TODO we need to deal with markdown in the descriptions
//noinspection deprecation
appInfo.setDescription(fixEncoding(rawDescriptionString));

String fixedDescription = fixEncoding(rawDescriptionString);
fixedDescription = fixMarkdown(fixedDescription);
fixedDescription = convertSubredditsToLinks(fixedDescription);

appInfo.setDescription(fixedDescription);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,65 @@

import android.text.Html;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EncodingFixer {
private static final Pattern URL_MARKDOWN_PATTERN = Pattern.compile("(\\[(.*?)\\]\\s{0,1}\\((http.*?)\\))");
private static final Pattern BOLD_MARKDOWN_PATTERN = Pattern.compile("(\\*\\*(.*?)\\*\\*)");
private static final Pattern SUBREDDIT_PATTERN = Pattern.compile("(\\/r\\/.*?(?!\\S))");

public String fixHtmlEscapes(String input) {
//noinspection deprecation
return Html.fromHtml(input).toString();
}

public String convertMarkdownToHtml(String input) {
input = urlMarkdownToHtml(input);
input = boldMarkdownToHtml(input);

return input;
}

public String convertSubredditsToLinks(String input) {
// Converts subreddit mentions to links in HTML
Matcher subredditMatcher = SUBREDDIT_PATTERN.matcher(input);
while (subredditMatcher.find()) {
String matchedSubreddit = subredditMatcher.group(1);

String fixedString = "<a href=\"https://www.reddit.com" + matchedSubreddit +
"\">" + matchedSubreddit + "</a>";

input = input.replaceAll(matchedSubreddit + "(?!\\S)", fixedString);
}
return input;
}

// Converts string with link markdown to HTML
private String urlMarkdownToHtml(String string) {
Matcher urlMarkdownMatcher = URL_MARKDOWN_PATTERN.matcher(string);
while (urlMarkdownMatcher.find()) {
String matchedString = urlMarkdownMatcher.group(2);
String matchedUrl = urlMarkdownMatcher.group(3);

String fixedString = "<a href=\"" + matchedUrl + "\">" + matchedString + "</a>";

string = string.replace(urlMarkdownMatcher.group(1), fixedString);
}
return string;
}

// Converts string with bold markdown to HTML
private String boldMarkdownToHtml(String string) {
Matcher boldMarkdownMatcher = BOLD_MARKDOWN_PATTERN.matcher(string);
while (boldMarkdownMatcher.find()) {
String matchedString = boldMarkdownMatcher.group(2);

String fixedString = "<b>" + matchedString + "</b>";

string = string.replace(boldMarkdownMatcher.group(1), fixedString);
}
return string;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
Expand All @@ -13,6 +14,8 @@
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
Expand Down Expand Up @@ -224,7 +227,14 @@ public void displayDetails(@Nullable AppInfo appInfo) {
secondaryTitle.setText(appInfo.getSecondaryCategory());
downloads = new ArrayList<>(appInfo.getDownloads());
contacts = new ArrayList<>(appInfo.getContacts());
description.setText(appInfo.getDescription());

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
description.setText(Html.fromHtml(appInfo.getDescription(), Html.FROM_HTML_MODE_COMPACT));
} else {
description.setText(Html.fromHtml(appInfo.getDescription()));
}
description.setMovementMethod(LinkMovementMethod.getInstance());

tagContainer.removeAllViews();
for (AppTags appTags : appInfo.getTags()) {
TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.view_tagtemplate, tagContainer, false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package subreddit.android.appstore.screens.list;

import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
Expand Down Expand Up @@ -91,7 +93,13 @@ public ViewHolder(View itemView) {

public void bind(AppInfo item) {
appName.setText(item.getAppName());
description.setText(item.getDescription());

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
description.setText(Html.fromHtml(item.getDescription(), Html.FROM_HTML_MODE_COMPACT));
} else {
description.setText(Html.fromHtml(item.getDescription()));
}

if (PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean(SettingsActivity.PREF_KEY_LOAD_MEDIA, true)) {
iconFrame.setVisibility(View.VISIBLE);
GlideApp.with(getContext())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.reactivex.subjects.ReplaySubject;
import subreddit.android.appstore.backend.data.AppInfo;
import subreddit.android.appstore.backend.data.AppTags;
import subreddit.android.appstore.backend.reddit.wiki.parser.EncodingFixer;


public class FakeWikiRepository implements WikiRepository {
Expand All @@ -28,6 +29,7 @@ public Observable<Collection<AppInfo>> getAppList() {
}

private Observable<Collection<AppInfo>> loadData() {
EncodingFixer encodingFixer = new EncodingFixer();
return Observable.defer(() -> {
Collection<AppInfo> testValues = new ArrayList<>();
AppInfo app1 = new AppInfo();
Expand All @@ -46,6 +48,31 @@ private Observable<Collection<AppInfo>> loadData() {
app2.addTag(AppTags.PAID);
app2.addTag(AppTags.NEW);
testValues.add(app2);

// Test app with bold and link markdown in description
AppInfo app3 = new AppInfo();
app3.setAppName("Markdown Test");
app3.setDescription(
encodingFixer.convertMarkdownToHtml("Tutorial **screencast** for [Propellerheads Reason](https://www.propellerheads.se/products/reason/)")
);
app3.setCategories(new ArrayList<String>(Arrays.asList("Testing", "Examples", "Descriptions")));
app3.addTag(AppTags.WEAR);
app3.addTag(AppTags.PAID);
app3.addTag(AppTags.NEW);
testValues.add(app3);

// Test app with Subreddit link conversion
AppInfo app4 = new AppInfo();
app4.setAppName("Subreddit Link Conversion Test");
app4.setDescription(
encodingFixer.convertSubredditsToLinks("Wow! /r/Android is the bestest ever.")
);
app4.setCategories(new ArrayList<String>(Arrays.asList("Testing", "Examples", "Descriptions")));
app4.addTag(AppTags.WEAR);
app4.addTag(AppTags.PAID);
app4.addTag(AppTags.NEW);
testValues.add(app4);

for (int i = 0; i < 1000; i++) {
AppInfo randomApp = new AppInfo();
randomApp.setAppName(UUID.randomUUID().toString() + " app");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,19 @@ public String answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArgument(0);
}
});
when(encodingFixer.convertMarkdownToHtml(anyString())).then(new Answer<String>() {
public String answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArgument(0);
}
});
when(encodingFixer.convertSubredditsToLinks(anyString())).then(new Answer<String>() {
public String answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArgument(0);
}
});
parser = new DescriptionColumnParser(encodingFixer);
}

@Test
public void testParse() throws IOException {
String rawDescriptionData = "This is a description";
Expand All @@ -44,15 +55,4 @@ public void testParse() throws IOException {
assertFalse(appInfo.getDescription().isEmpty());
assertEquals("This is a description", appInfo.getDescription());
}

@Test
public void testParse_withMarkdown() throws IOException {
String rawDescriptionData = "";
Map<AppParser.Column, String> rawColumnMap = new HashMap<>();
rawColumnMap.put(AppParser.Column.DESCRIPTION, rawDescriptionData);
AppInfo appInfo = new AppInfo();
parser.parse(appInfo, rawColumnMap);

//TODO: Implement markdown parsing in DescriptionColumnParser as well
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package subreddit.android.appstore.backend.reddit.wiki.parser;

import org.junit.Before;
import org.junit.Test;

import static junit.framework.Assert.assertEquals;

public class EncodingFixerTest {
private String descriptionBoldMarkdown, descriptionLinkMarkdown, descriptionSubreddits;
private EncodingFixer encodingFixer;

@Before
public void setup() {
encodingFixer = new EncodingFixer();

descriptionBoldMarkdown = "New ingenious full feature Reddit client. **Read AMA in magazine" +
"style QA format.** Add upcoming AMA to your calendar. **Only app with Text to " +
"Speech: Listen to the content.** Zero wait, streams all GIF instantly.";
descriptionLinkMarkdown = "Tutorial screencast for [Propellerheads Reason](https://www" +
".propellerheads.se/products/reason/) and [Link 2](https://www.google.com)";
descriptionSubreddits = "You can setup playlists, geofences, volume control, and share" +
" playlists with others. Share playlists at /r/androidrrm and /r/android";
}

@Test
public void testConvertMarkdownToHtml() {
String output = encodingFixer.convertMarkdownToHtml("");
assertEquals("", output);

output = encodingFixer.convertMarkdownToHtml("This description has no markdown");
assertEquals("This description has no markdown", output);
}

@Test
public void testConvertMarkdownToHtml_bold() {
String output = encodingFixer.convertMarkdownToHtml(descriptionBoldMarkdown);
assertEquals("New ingenious full feature Reddit client. <b>Read AMA in magazine" +
"style QA format.</b> Add upcoming AMA to your calendar. <b>Only app with Text to " +
"Speech: Listen to the content.</b> Zero wait, streams all GIF instantly.", output);
}

@Test
public void testConvertMarkdownToHtml_link() {
String output = encodingFixer.convertMarkdownToHtml(descriptionLinkMarkdown);
assertEquals("Tutorial screencast for " +
"<a href=\"https://www.propellerheads.se/products/reason/\">" +
"Propellerheads Reason</a> and <a href=\"https://www.google.com\">Link 2</a>", output);
}

@Test
public void testConvertSubredditsToLinks() {
String output = encodingFixer.convertSubredditsToLinks(descriptionSubreddits);
assertEquals("You can setup playlists, geofences, volume control, and share " +
"playlists with others. Share playlists at " +
"<a href=\"https://www.reddit.com/r/androidrrm\">/r/androidrrm</a> " +
"and <a href=\"https://www.reddit.com/r/android\">/r/android</a>", output);
}
}

0 comments on commit c1b972b

Please sign in to comment.