Browse Source

Merge branch 'bbparser' into develop

# Conflicts:
#	app/build.gradle
pull/61/merge
oogee 6 years ago
parent
commit
81e3af098a
  1. 22
      app/src/main/AndroidManifest.xml
  2. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  3. 38
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  4. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  5. 53
      app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java
  6. 58
      app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java
  7. 19
      app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
  8. 221
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java

22
app/src/main/AndroidManifest.xml

@ -17,11 +17,18 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<activity
android:name=".activities.main.MainActivity"
@ -118,8 +125,8 @@
</activity>
<activity
android:name=".activities.bookmarks.BookmarkActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -127,8 +134,8 @@
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:parentActivityName=".activities.main.MainActivity"
android:launchMode="singleTop"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.PreferenceTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -166,6 +173,7 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity" />
</activity>
<activity android:name=".activities.TestActivity"></activity>
</application>
</manifest>

3
app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java

@ -19,6 +19,7 @@ import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.TestActivity;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.forum.ForumFragment;
@ -132,6 +133,8 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
, Toast.LENGTH_SHORT).show();
}
mBackPressed = System.currentTimeMillis();
startActivity(new Intent(this, TestActivity.class));
}
@Override

38
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java

@ -65,6 +65,7 @@ import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.parsing.ThmmyParser;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import timber.log.Timber;
@ -165,9 +166,34 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
Poll poll = (Poll) topicItems.get(position);
Poll.Entry[] entries = poll.getEntries();
PollViewHolder holder = (PollViewHolder) currentHolder;
boolean pollSupported = true;
for (Poll.Entry entry : entries) {
if (ThmmyParser.containsHtml(entry.getEntryName())) pollSupported = false;
break;
}
if (ThmmyParser.containsHtml(poll.getQuestion())) pollSupported = false;
if (!pollSupported) {
holder.optionsLayout.setVisibility(View.GONE);
holder.voteChart.setVisibility(View.GONE);
holder.removeVotesButton.setVisibility(View.GONE);
holder.showPollResultsButton.setVisibility(View.GONE);
holder.hidePollResultsButton.setVisibility(View.GONE);
// use the submit vote button to open poll on browser
holder.submitButton.setText("Open in browser");
holder.submitButton.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(viewModel.getTopicUrl()));
context.startActivity(browserIntent);
});
holder.submitButton.setVisibility(View.VISIBLE);
// put a warning instead of a question
holder.question.setText("This topic contains a poll that is not supported in mthmmy");
return;
}
holder.question.setText(poll.getQuestion());
holder.optionsLayout.removeAllViews();
holder.errorTooManySelected.setVisibility(View.GONE);
holder.errorTextview.setVisibility(View.GONE);
if (poll.getAvailableVoteCount() > 1) {
for (Poll.Entry entry : entries) {
LinearLayout container = new LinearLayout(context);
@ -182,6 +208,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//noinspection deprecation
label.setText(Html.fromHtml(entry.getEntryName()));
}
label.setText(ThmmyParser.html2span(context, entry.getEntryName()));
checkBox.setTextColor(context.getResources().getColor(R.color.primary_text));
container.addView(checkBox);
container.addView(label);
@ -201,6 +228,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//noinspection deprecation
radioButton.setText(Html.fromHtml(entries[i].getEntryName()));
}
radioButton.setText(ThmmyParser.html2span(context, entries[i].getEntryName()));
radioButton.setTextColor(context.getResources().getColor(R.color.primary_text));
radioGroup.addView(radioButton);
}
@ -260,10 +288,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (poll.getPollFormUrl() != null) {
holder.submitButton.setOnClickListener(v -> {
if (!viewModel.submitVote(holder.optionsLayout)) {
holder.errorTooManySelected.setText(context.getResources()
holder.errorTextview.setText(context.getResources()
.getQuantityString(R.plurals.error_too_many_checked, poll.getAvailableVoteCount(),
poll.getAvailableVoteCount()));
holder.errorTooManySelected.setVisibility(View.VISIBLE);
holder.errorTextview.setVisibility(View.VISIBLE);
}
});
holder.submitButton.setVisibility(View.VISIBLE);
@ -765,7 +793,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
static class PollViewHolder extends RecyclerView.ViewHolder {
final TextView question, errorTooManySelected;
final TextView question, errorTextview;
final LinearLayout optionsLayout;
final AppCompatButton submitButton;
final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton;
@ -780,7 +808,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
removeVotesButton = itemView.findViewById(R.id.remove_vote_button);
showPollResultsButton = itemView.findViewById(R.id.show_poll_results_button);
hidePollResultsButton = itemView.findViewById(R.id.show_poll_options_button);
errorTooManySelected = itemView.findViewById(R.id.error_too_many_checked);
errorTextview = itemView.findViewById(R.id.error_too_many_checked);
voteChart = itemView.findViewById(R.id.vote_chart);
}
}

6
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java

@ -156,9 +156,9 @@ public class TopicParser {
ArrayList<TopicItem> parsedPostsList = new ArrayList<>();
// Poll poll = findPoll(topic);
// if (poll != null)
// parsedPostsList.add(poll);
Poll poll = findPoll(topic);
if (poll != null)
parsedPostsList.add(poll);
Elements postRows;

53
app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java

@ -0,0 +1,53 @@
package gr.thmmy.mthmmy.model;
import androidx.annotation.NonNull;
public class BBTag {
private int start, end;
private String name, attribute;
public BBTag(int start, String name) {
this.start = start;
this.name = name;
}
public BBTag(int start, String name, String attribute) {
this.start = start;
this.name = name;
this.attribute = attribute;
}
@NonNull
@Override
public String toString() {
return "start:" + start + ",end:" + end + ",name:" + name;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAttribute() {
return attribute;
}
}

58
app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java

@ -0,0 +1,58 @@
package gr.thmmy.mthmmy.model;
import androidx.annotation.NonNull;
public class HtmlTag {
private int start, end;
private String name, attributeKey, attributeValue;
public HtmlTag(int start, String name) {
this.start = start;
this.name = name;
}
public HtmlTag(int start, String name, String attributeKey, String attributeValue) {
this.start = start;
this.name = name;
this.attributeKey = attributeKey;
this.attributeValue = attributeValue;
}
@NonNull
@Override
public String toString() {
return "start:" + start + ",end:" + end + ",name:" + name;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAttributeKey() {
return attributeKey;
}
public String getAttributeValue() {
return attributeValue;
}
}

19
app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
@ -12,6 +13,7 @@ import android.text.style.URLSpan;
import android.view.View;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.ThmmyPage;
@ -41,7 +43,7 @@ public class HTMLUtils {
return strBuilder;
}
private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) {
public static void makeLinkClickable(Context context, SpannableStringBuilder strBuilder, final URLSpan span) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
@ -50,24 +52,27 @@ public class HTMLUtils {
public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class);
Intent intent = new Intent(context, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class);
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
activity.getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX))
activity.finish();
context.startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX)) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
};
strBuilder.setSpan(clickable, start, end, flags);

221
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java

@ -0,0 +1,221 @@
package gr.thmmy.mthmmy.utils.parsing;
import android.content.Context;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import java.nio.charset.UnsupportedCharsetException;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import gr.thmmy.mthmmy.model.BBTag;
import gr.thmmy.mthmmy.model.HtmlTag;
import gr.thmmy.mthmmy.utils.HTMLUtils;
public class ThmmyParser {
private static final String[] ALL_BB_TAGS = {"b", "i", "u", "s", "glow", "shadow", "move", "pre", "lefter",
"center", "right", "hr", "size", "font", "color", "youtube", "flash", "img", "url"
, "email", "ftp", "table", "tr", "td", "sup", "sub", "tt", "code", "quote", "tex", "list", "li"};
private static final String[] ALL_HTML_TAGS = {"b", "br", "span", "i", "div", "del", "marquee", "pre",
"hr", "embed", "noembed", "a", "img", "table", "tr", "td", "sup", "sub", "tt", "pre", "ul", "li"};
public static SpannableStringBuilder bb2span(String bb) {
SpannableStringBuilder builder = new SpannableStringBuilder(bb);
// store the original indices of the string
LinkedList<Integer> stringIndices = new LinkedList<>();
for (int i = 0; i < builder.length(); i++) {
stringIndices.add(i);
}
BBTag[] tags = getBBTags(bb);
for (BBTag tag : tags) {
int start = stringIndices.indexOf(tag.getStart());
int end = stringIndices.indexOf(tag.getEnd());
int startTagLength = tag.getName().length() + 2;
int endTagLength = tag.getName().length() + 3;
switch (tag.getName()) {
case "b":
builder.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "i":
builder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "u":
builder.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "s":
builder.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
throw new UnsupportedCharsetException("Tag not supported");
}
//remove starting and ending tag and and do the same changes in the list
builder.delete(start, start + startTagLength);
for (int i = start; i < start + startTagLength; i++) {
stringIndices.remove(start);
}
builder.delete(end - startTagLength, end - startTagLength + endTagLength);
for (int i = end - startTagLength; i < end - startTagLength + endTagLength; i++) {
stringIndices.remove(end - startTagLength);
}
}
return builder;
}
public static SpannableStringBuilder html2span(Context context, String html) {
SpannableStringBuilder builder = new SpannableStringBuilder(html);
// store the original indices of the string
LinkedList<Integer> stringIndices = new LinkedList<>();
for (int i = 0; i < builder.length(); i++) {
stringIndices.add(i);
}
HtmlTag[] tags = getHtmlTags(html);
for (HtmlTag tag : tags) {
int start = stringIndices.indexOf(tag.getStart());
int end = stringIndices.indexOf(tag.getEnd());
int startTagLength = tag.getName().length() + 2;
if (tag.getAttributeKey() != null) {
startTagLength += tag.getAttributeKey().length() + tag.getAttributeValue().length() + 4;
}
int endTagLength = tag.getName().length() + 3;
if (isHtmlTagSupported(tag.getName(), tag.getAttributeKey(), tag.getAttributeValue())) {
switch (tag.getName()) {
case "b":
builder.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "i":
builder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "span":
if (tag.getAttributeKey().equals("style") && tag.getAttributeValue().equals("text-decoration: underline;")) {
builder.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
break;
case "del":
builder.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case "a":
URLSpan urlSpan = new URLSpan(tag.getAttributeValue());
builder.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
HTMLUtils.makeLinkClickable(context, builder, urlSpan);
break;
default:
throw new UnsupportedCharsetException("Tag not supported");
}
}
//remove starting and ending tag and and do the same changes in the list
builder.delete(start, start + startTagLength);
for (int i = start; i < start + startTagLength; i++) {
stringIndices.remove(start);
}
builder.delete(end - startTagLength, end - startTagLength + endTagLength);
for (int i = end - startTagLength; i < end - startTagLength + endTagLength; i++) {
stringIndices.remove(end - startTagLength);
}
}
return builder;
}
public static BBTag[] getBBTags(String bb) {
Pattern bbtagPattern = Pattern.compile("\\[(.+?)\\]");
LinkedList<BBTag> tags = new LinkedList<>();
Matcher bbMatcher = bbtagPattern.matcher(bb);
while (bbMatcher.find()) {
String startTag = bbMatcher.group(1);
int separatorIndex = startTag.indexOf('=');
String name, attribute = null;
if (separatorIndex > 0) {
attribute = startTag.substring(separatorIndex);
name = startTag.substring(0, separatorIndex);
} else
name = startTag;
if (name.startsWith("/")) {
//closing tag
name = name.substring(1);
for (int i = tags.size() - 1; i >= 0; i--) {
if (tags.get(i).getName().equals(name)) {
tags.get(i).setEnd(bbMatcher.start());
break;
}
}
continue;
}
if (isBBTagSupported(name))
tags.add(new BBTag(bbMatcher.start(), name, attribute));
}
// remove parsed tags with no end tag
for (BBTag bbTag : tags)
if (bbTag.getEnd() == 0)
tags.remove(bbTag);
return tags.toArray(new BBTag[0]);
}
public static HtmlTag[] getHtmlTags(String html) {
Pattern htmlPattern = Pattern.compile("<(.+?)>");
LinkedList<HtmlTag> tags = new LinkedList<>();
Matcher htmlMatcher = htmlPattern.matcher(html);
while (htmlMatcher.find()) {
String startTag = htmlMatcher.group(1);
int separatorIndex = startTag.indexOf(' ');
String name, attribute = null, attributeValue = null;
if (separatorIndex > 0) {
String fullAttribute = startTag.substring(separatorIndex);
int equalsIndex = fullAttribute.indexOf('=');
attribute = fullAttribute.substring(1, equalsIndex);
attributeValue = fullAttribute.substring(equalsIndex + 2, fullAttribute.length() - 1);
name = startTag.substring(0, separatorIndex);
} else
name = startTag;
if (name.startsWith("/")) {
//closing tag
name = name.substring(1);
for (int i = tags.size() - 1; i >= 0; i--) {
if (tags.get(i).getName().equals(name)) {
tags.get(i).setEnd(htmlMatcher.start());
break;
}
}
continue;
}
if (isHtmlTag(name))
tags.add(new HtmlTag(htmlMatcher.start(), name, attribute, attributeValue));
}
// remove parsed tags with no end tag
for (HtmlTag htmlTag : tags)
if (htmlTag.getEnd() == 0)
tags.remove(htmlTag);
return tags.toArray(new HtmlTag[0]);
}
private static boolean isHtmlTagSupported(String name, String attribute, String attributeValue) {
return name.equals("b") || name.equals("i") || name.equals("span") || name.equals("del") || name.equals("a");
}
public static boolean isBBTagSupported(String name) {
return name.equals("b") || name.equals("i") || name.equals("u") || name.equals("s");
}
public static boolean isHtmlTag(String tagName) {
for (String tag : ALL_HTML_TAGS)
if (TextUtils.equals(tag, tagName)) return true;
return false;
}
public static boolean containsHtml(String s) {
return getHtmlTags(s).length > 0;
}
}
Loading…
Cancel
Save