diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eef46aad..cf3aaef0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,11 +17,18 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
index 85cb4a9c..f8d87263 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
+++ b/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
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
index 5a1fdc5a..f5bcb761 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
+++ b/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 {
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 {
//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 {
//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 {
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 {
}
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 {
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);
}
}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
index 5a411e12..4ebbba5a 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
@@ -156,9 +156,9 @@ public class TopicParser {
ArrayList 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;
diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java b/app/src/main/java/gr/thmmy/mthmmy/model/BBTag.java
new file mode 100644
index 00000000..77504761
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java b/app/src/main/java/gr/thmmy/mthmmy/model/HtmlTag.java
new file mode 100644
index 00000000..b7e13021
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
index 2713a2a8..8ca4ede6 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
+++ b/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);
diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java b/app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ThmmyParser.java
new file mode 100644
index 00000000..f900290f
--- /dev/null
+++ b/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 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 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 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 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;
+ }
+}