Browse Source

Merge pull request #55 from ThmmyNoLife/polls

Polls
pull/61/merge
oogee 6 years ago
committed by GitHub
parent
commit
a1de7ffe48
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  2. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  3. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  4. 66
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  5. 886
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  6. 96
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  7. 6
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java
  8. 20
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/RemoveVoteTask.java
  9. 48
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/SubmitVoteTask.java
  10. 5
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
  11. 7
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
  12. 108
      app/src/main/java/gr/thmmy/mthmmy/model/Poll.java
  13. 2
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  14. 10
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  15. 5
      app/src/main/java/gr/thmmy/mthmmy/model/TopicItem.java
  16. 62
      app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java
  17. 32
      app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java
  18. 17
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/NewParseTask.java
  19. 13
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java
  20. 97
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
  21. 78
      app/src/main/res/layout/activity_topic_poll.xml
  22. 4
      app/src/main/res/values/strings.xml

8
app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java

@ -187,13 +187,13 @@ public class ForumFragment extends BaseFragment {
private class ForumTask extends NewParseTask<ArrayList<Category>> { private class ForumTask extends NewParseTask<ArrayList<Category>> {
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
public ForumTask(OnParseTaskStartedListener onParseTaskStartedListener, public ForumTask(OnTaskStartedListener onTaskStartedListener,
OnParseTaskFinishedListener<ArrayList<Category>> onParseTaskFinishedListener) { OnNetworkTaskFinishedListener<ArrayList<Category>> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskFinishedListener); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
@Override @Override
protected ArrayList<Category> parse(Document document) throws ParseException { protected ArrayList<Category> parse(Document document, Response response) throws ParseException {
Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]"); Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]");
if (categoryBlocks.size() != 0) { if (categoryBlocks.size() != 0) {
ArrayList<Category> fetchedCategories = new ArrayList<>(); ArrayList<Category> fetchedCategories = new ArrayList<>();

8
app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java

@ -157,13 +157,13 @@ public class RecentFragment extends BaseFragment {
//---------------------------------------ASYNC TASK----------------------------------- //---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> { private class RecentTask extends NewParseTask<ArrayList<TopicSummary>> {
public RecentTask(OnParseTaskStartedListener onParseTaskStartedListener, public RecentTask(OnTaskStartedListener onTaskStartedListener,
OnParseTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) { OnNetworkTaskFinishedListener<ArrayList<TopicSummary>> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskFinishedListener); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
@Override @Override
protected ArrayList<TopicSummary> parse(Document document) throws ParseException { protected ArrayList<TopicSummary> parse(Document document, Response response) throws ParseException {
ArrayList<TopicSummary> fetchedRecent = new ArrayList<>(); ArrayList<TopicSummary> fetchedRecent = new ArrayList<>();
Elements recent = document.select("#block8 :first-child div"); Elements recent = document.select("#block8 :first-child div");
if (!recent.isEmpty()) { if (!recent.isEmpty()) {

6
app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java

@ -185,12 +185,12 @@ public class UnreadFragment extends BaseFragment {
private class UnreadTask extends NewParseTask<Void> { private class UnreadTask extends NewParseTask<Void> {
UnreadTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskFinishedListener<Void> onParseTaskFinishedListener) { UnreadTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskFinishedListener); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
@Override @Override
protected Void parse(Document document) throws ParseException { protected Void parse(Document document, Response response) throws ParseException {
Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)"); Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)");
if (!unread.isEmpty()) { if (!unread.isEmpty()) {
//topicSummaries.clear(); //topicSummaries.clear();

66
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java

@ -48,9 +48,11 @@ import gr.thmmy.mthmmy.editorview.EmojiKeyboard;
import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.CustomLinearLayoutManager; import gr.thmmy.mthmmy.utils.CustomLinearLayoutManager;
import gr.thmmy.mthmmy.utils.HTMLUtils; import gr.thmmy.mthmmy.utils.HTMLUtils;
import gr.thmmy.mthmmy.utils.NetworkResultCodes; import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel; import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
@ -84,7 +86,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
/** /**
* Holds a list of this topic's posts * Holds a list of this topic's posts
*/ */
private ArrayList<Post> postsList; private ArrayList<TopicItem> topicItems;
//Reply related //Reply related
private FloatingActionButton replyFAB; private FloatingActionButton replyFAB;
//Topic's pages related //Topic's pages related
@ -169,7 +171,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
emojiKeyboard = findViewById(R.id.emoji_keyboard); emojiKeyboard = findViewById(R.id.emoji_keyboard);
postsList = new ArrayList<>(); topicItems = new ArrayList<>();
recyclerView = findViewById(R.id.topic_recycler_view); recyclerView = findViewById(R.id.topic_recycler_view);
recyclerView.setHasFixedSize(true); recyclerView.setHasFixedSize(true);
@ -178,7 +180,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
getApplicationContext(), topicPageUrl); getApplicationContext(), topicPageUrl);
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
topicAdapter = new TopicAdapter(this, postsList); topicAdapter = new TopicAdapter(this, topicItems);
recyclerView.setAdapter(topicAdapter); recyclerView.setAdapter(topicAdapter);
replyFAB = findViewById(R.id.topic_fab); replyFAB = findViewById(R.id.topic_fab);
@ -279,15 +281,15 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
} }
return; return;
} else if (viewModel.isWritingReply()) { } else if (viewModel.isWritingReply()) {
postsList.remove(postsList.size() - 1); topicItems.remove(topicItems.size() - 1);
topicAdapter.notifyItemRemoved(postsList.size()); topicAdapter.notifyItemRemoved(topicItems.size());
topicAdapter.setBackButtonHidden(); topicAdapter.setBackButtonHidden();
viewModel.setWritingReply(false); viewModel.setWritingReply(false);
replyFAB.show(); replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
return; return;
} else if (viewModel.isEditingPost()) { } else if (viewModel.isEditingPost()) {
postsList.get(viewModel.getPostBeingEditedPosition()).setPostType(Post.TYPE_POST); ((Post) topicItems.get(viewModel.getPostBeingEditedPosition())).setPostType(Post.TYPE_POST);
topicAdapter.notifyItemChanged(viewModel.getPostBeingEditedPosition()); topicAdapter.notifyItemChanged(viewModel.getPostBeingEditedPosition());
topicAdapter.setBackButtonHidden(); topicAdapter.setBackButtonHidden();
viewModel.setEditingPost(false); viewModel.setEditingPost(false);
@ -420,7 +422,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
} else if (rect != null && event.getAction() == MotionEvent.ACTION_UP && autoIncrement) { } else if (rect != null && event.getAction() == MotionEvent.ACTION_UP && autoIncrement) {
autoIncrement = false; autoIncrement = false;
paginationEnabled(true); paginationEnabled(true);
viewModel.performPageChange(); viewModel.loadPageIndicated();
} else if (rect != null && event.getAction() == MotionEvent.ACTION_MOVE) { } else if (rect != null && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false; autoIncrement = false;
@ -464,7 +466,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
} else if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) { } else if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) {
autoDecrement = false; autoDecrement = false;
paginationEnabled(true); paginationEnabled(true);
viewModel.performPageChange(); viewModel.loadPageIndicated();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) { } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (rect != null && if (rect != null &&
!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
@ -529,7 +531,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
replyFAB.show(); replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setWritingReply(false); viewModel.setWritingReply(false);
if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0) { if ((((Post) topicItems.get(topicItems.size() - 1)).getPostNumber() + 1) % 15 == 0) {
Timber.i("Reply was posted in new page. Switching to last page."); Timber.i("Reply was posted in new page. Switching to last page.");
viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647); viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647);
} else { } else {
@ -538,8 +540,8 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
} else { } else {
Timber.w("Post reply unsuccessful"); Timber.w("Post reply unsuccessful");
Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show();
recyclerView.getChildAt(postsList.size() - 1).setAlpha(1); recyclerView.getChildAt(topicItems.size() - 1).setAlpha(1);
recyclerView.getChildAt(postsList.size() - 1).setEnabled(true); recyclerView.getChildAt(topicItems.size() - 1).setEnabled(true);
} }
} }
}); });
@ -572,7 +574,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
if (result) { if (result) {
Timber.i("Post edit successful"); Timber.i("Post edit successful");
postsList.get(position).setPostType(Post.TYPE_POST); ((Post) topicItems.get(position)).setPostType(Post.TYPE_POST);
topicAdapter.notifyItemChanged(position); topicAdapter.notifyItemChanged(position);
replyFAB.show(); replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
@ -597,6 +599,30 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
} }
}); });
viewModel.setVoteTaskStartedListener(() -> progressBar.setVisibility(ProgressBar.VISIBLE));
viewModel.setVoteTaskFinishedListener((resultCode, data) -> {
progressBar.setVisibility(View.GONE);
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
Timber.i("Vote sent");
viewModel.resetPage();
}
else {
Timber.w("Failed to send vote");
Toast.makeText(this, "Failed to send vote", Toast.LENGTH_LONG).show();
}
});
viewModel.setRemoveVoteTaskStartedListener(() -> progressBar.setVisibility(ProgressBar.VISIBLE));
viewModel.setRemoveVoteTaskFinishedListener((resultCode, data) -> {
progressBar.setVisibility(View.GONE);
if (resultCode == NetworkResultCodes.SUCCESSFUL) {
Timber.i("Vote removed");
viewModel.resetPage();
}
else {
Timber.w("Failed to remove vote");
Toast.makeText(this, "Failed to remove vote", Toast.LENGTH_LONG).show();
}
});
// observe the chages in data // observe the chages in data
viewModel.getPageIndicatorIndex().observe(this, pageIndicatorIndex -> { viewModel.getPageIndicatorIndex().observe(this, pageIndicatorIndex -> {
if (pageIndicatorIndex == null) return; if (pageIndicatorIndex == null) return;
@ -622,11 +648,11 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
else else
replyFAB.show(); replyFAB.show();
}); });
viewModel.getPostsList().observe(this, postList -> { viewModel.getTopicItems().observe(this, postList -> {
if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE); if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE);
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
postsList.clear(); topicItems.clear();
postsList.addAll(postList); topicItems.addAll(postList);
topicAdapter.notifyDataSetChanged(); topicAdapter.notifyDataSetChanged();
}); });
/*viewModel.getFocusedPostIndex().observe(this, focusedPostIndex -> { /*viewModel.getFocusedPostIndex().observe(this, focusedPostIndex -> {
@ -643,7 +669,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
break; break;
case NETWORK_ERROR: case NETWORK_ERROR:
Timber.w("Network error on loaded page"); Timber.w("Network error on loaded page");
if (viewModel.getPostsList().getValue() == null) { if (viewModel.getTopicItems().getValue() == null) {
// no page has been loaded yet. Give user the ability to refresh // no page has been loaded yet. Give user the ability to refresh
recyclerView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
TextView errorTextview = findViewById(R.id.error_textview); TextView errorTextview = findViewById(R.id.error_textview);
@ -701,9 +727,9 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
Timber.i("Prepare for reply successful"); Timber.i("Prepare for reply successful");
//prepare for a reply //prepare for a reply
viewModel.setWritingReply(true); viewModel.setWritingReply(true);
postsList.add(Post.newQuickReply()); topicItems.add(Post.newQuickReply());
topicAdapter.notifyItemInserted(postsList.size()); topicAdapter.notifyItemInserted(topicItems.size());
recyclerView.scrollToPosition(postsList.size() - 1); recyclerView.scrollToPosition(topicItems.size() - 1);
replyFAB.hide(); replyFAB.hide();
bottomNavBar.setVisibility(View.GONE); bottomNavBar.setVisibility(View.GONE);
} else { } else {
@ -716,7 +742,7 @@ public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFo
if (result != null && result.isSuccessful()) { if (result != null && result.isSuccessful()) {
Timber.i("Prepare for edit successful"); Timber.i("Prepare for edit successful");
viewModel.setEditingPost(true); viewModel.setEditingPost(true);
postsList.get(result.getPosition()).setPostType(Post.TYPE_EDIT); ((Post) topicItems.get(result.getPosition())).setPostType(Post.TYPE_EDIT);
topicAdapter.notifyItemChanged(result.getPosition()); topicAdapter.notifyItemChanged(result.getPosition());
recyclerView.scrollToPosition(result.getPosition()); recyclerView.scrollToPosition(result.getPosition());
replyFAB.hide(); replyFAB.hide();

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

@ -15,9 +15,11 @@ import android.support.annotation.NonNull;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.content.res.AppCompatResources; import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -27,16 +29,26 @@ import android.view.inputmethod.InputMethodManager;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -46,9 +58,11 @@ import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.editorview.EditorView; import gr.thmmy.mthmmy.editorview.EditorView;
import gr.thmmy.mthmmy.editorview.EmojiKeyboard; import gr.thmmy.mthmmy.editorview.EmojiKeyboard;
import gr.thmmy.mthmmy.model.Poll;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel; import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
@ -76,16 +90,16 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context; private final Context context;
private final OnPostFocusChangeListener postFocusListener; private final OnPostFocusChangeListener postFocusListener;
private final EmojiKeyboard.EmojiKeyboardOwner emojiKeyboardOwner; private final EmojiKeyboard.EmojiKeyboardOwner emojiKeyboardOwner;
private final List<Post> postsList; private final List<TopicItem> topicItems;
private TopicViewModel viewModel; private TopicViewModel viewModel;
/** /**
* @param context the context of the {@link RecyclerView} * @param context the context of the {@link RecyclerView}
* @param postsList List of {@link Post} objects to use * @param topicItems List of {@link Post} objects to use
*/ */
TopicAdapter(TopicActivity context, List<Post> postsList) { TopicAdapter(TopicActivity context, List<TopicItem> topicItems) {
this.context = context; this.context = context;
this.postsList = postsList; this.topicItems = topicItems;
this.postFocusListener = context; this.postFocusListener = context;
this.emojiKeyboardOwner = context; this.emojiKeyboardOwner = context;
@ -96,7 +110,8 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return postsList.get(position).getPostType(); if (topicItems.get(position) instanceof Poll) return Poll.TYPE_POLL;
return ((Post) topicItems.get(position)).getPostType();
} }
@NonNull @NonNull
@ -132,6 +147,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
editPostEdittext.requestFocus(); editPostEdittext.requestFocus();
return new EditMessageViewHolder(view); return new EditMessageViewHolder(view);
} else if (viewType == Poll.TYPE_POLL) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.activity_topic_poll, parent, false);
return new PollViewHolder(view);
} else { } else {
throw new IllegalArgumentException("Unknown view type"); throw new IllegalArgumentException("Unknown view type");
} }
@ -141,414 +160,496 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override @Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder currentHolder, public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder currentHolder,
final int position) { final int position) {
if (currentHolder instanceof PostViewHolder) { if (currentHolder.getItemViewType() == Poll.TYPE_POLL) {
final Post currentPost = postsList.get(position); Poll poll = (Poll) topicItems.get(position);
final PostViewHolder holder = (PostViewHolder) currentHolder; Poll.Entry[] entries = poll.getEntries();
PollViewHolder holder = (PollViewHolder) currentHolder;
//Post's WebView parameters holder.question.setText(poll.getQuestion());
holder.post.setClickable(true); holder.optionsLayout.removeAllViews();
holder.post.setWebViewClient(new LinkLauncher()); if (poll.getAvailableVoteCount() > 1) {
for (Poll.Entry entry : entries) {
//Avoids errors about layout having 0 width/height CheckBox checkBox = new CheckBox(context);
holder.thumbnail.setMinimumWidth(1); checkBox.setText(entry.getEntryName());
holder.thumbnail.setMinimumHeight(1); checkBox.setTextColor(context.getResources().getColor(R.color.primary_text));
//Sets thumbnail size holder.optionsLayout.addView(checkBox);
holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE);
holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE);
//noinspection ConstantConditions
Picasso.with(context)
.load(currentPost.getThumbnailURL())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
//Sets username,submit date, index number, subject, post's and attached files texts
holder.username.setText(currentPost.getAuthor());
holder.postDate.setText(currentPost.getPostDate());
if (currentPost.getPostNumber() != 0)
holder.postNum.setText(context.getString(
R.string.user_number_of_posts, currentPost.getPostNumber()));
else
holder.postNum.setText("");
holder.subject.setText(currentPost.getSubject());
holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null);
if ((currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0)
|| (currentPost.getLastEdit() != null)) {
holder.bodyFooterDivider.setVisibility(View.VISIBLE);
holder.postFooter.removeAllViews();
if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) {
int filesTextColor;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
filesTextColor = context.getResources().getColor(R.color.accent, null);
} else //noinspection deprecation
filesTextColor = context.getResources().getColor(R.color.accent);
for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) {
final TextView attached = new TextView(context);
attached.setTextSize(10f);
attached.setClickable(true);
attached.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
attached.setText(faIconFromFilename(attachedFile.getFilename()) + " "
+ attachedFile.getFilename() + attachedFile.getFileInfo());
attached.setTextColor(filesTextColor);
attached.setPadding(0, 3, 0, 3);
attached.setOnClickListener(view -> ((BaseActivity) context).downloadFile(attachedFile));
holder.postFooter.addView(attached);
}
} }
if (currentPost.getLastEdit() != null && currentPost.getLastEdit().length() > 0) { holder.voteChart.setVisibility(View.GONE);
int lastEditTextColor; holder.optionsLayout.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { } else if (poll.getAvailableVoteCount() == 1) {
lastEditTextColor = context.getResources().getColor(R.color.white, null); RadioGroup radioGroup = new RadioGroup(context);
} else //noinspection deprecation for (int i = 0; i < entries.length; i++) {
lastEditTextColor = context.getResources().getColor(R.color.white); RadioButton radioButton = new RadioButton(context);
radioButton.setId(i);
final TextView lastEdit = new TextView(context); radioButton.setText(entries[i].getEntryName());
lastEdit.setTextSize(12f); radioButton.setTextColor(context.getResources().getColor(R.color.primary_text));
lastEdit.setText(currentPost.getLastEdit()); radioGroup.addView(radioButton);
lastEdit.setTextColor(lastEditTextColor);
lastEdit.setPadding(0, 3, 0, 3);
holder.postFooter.addView(lastEdit);
} }
holder.optionsLayout.addView(radioGroup);
holder.voteChart.setVisibility(View.GONE);
holder.optionsLayout.setVisibility(View.VISIBLE);
} else { } else {
holder.bodyFooterDivider.setVisibility(View.GONE); //Showing results
holder.postFooter.removeAllViews(); holder.optionsLayout.setVisibility(View.GONE);
List<BarEntry> valuesToCompare = new ArrayList<>();
for (int i = 0; i < entries.length; i++) {
valuesToCompare.add(new BarEntry(i, entries[i].getVotes()));
}
BarDataSet data = new BarDataSet(valuesToCompare, "Vote Results");
data.setColor(context.getResources().getColor(R.color.accent));
YAxis yAxisLeft = holder.voteChart.getAxisLeft();
yAxisLeft.setGranularity(1f);
yAxisLeft.setTextColor(context.getResources().getColor(R.color.primary_text));
yAxisLeft.setAxisMinimum(0);
YAxis yAxisRight = holder.voteChart.getAxisRight();
yAxisRight.setEnabled(false);
XAxis xAxis = holder.voteChart.getXAxis();
xAxis.setValueFormatter((value, axis) -> entries[(int) value].getEntryName());
xAxis.setTextColor(context.getResources().getColor(R.color.primary_text));
xAxis.setGranularity(1f);
xAxis.setDrawGridLines(false);
xAxis.setDrawAxisLine(false);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
BarData barData = new BarData(data);
barData.setValueTextColor(context.getResources().getColor(R.color.accent));
holder.voteChart.setData(barData);
holder.voteChart.getLegend().setEnabled(false);
holder.voteChart.getDescription().setEnabled(false);
int chartHeightdp = 10 + 30 * entries.length;
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
holder.voteChart.setMinimumHeight((int) (chartHeightdp * (metrics.densityDpi / 160f)));
holder.voteChart.invalidate();
holder.voteChart.setVisibility(View.VISIBLE);
} }
if (poll.getRemoveVoteUrl() != null) {
holder.removeVotesButton.setOnClickListener(v -> viewModel.removeVote());
holder.removeVotesButton.setVisibility(View.VISIBLE);
} else holder.removeVotesButton.setVisibility(View.GONE);
if (poll.getShowVoteResultsUrl() != null) {
holder.showPollResultsButton.setOnClickListener(v -> viewModel.loadUrl(poll.getShowVoteResultsUrl()));
holder.showPollResultsButton.setVisibility(View.VISIBLE);
} else holder.showPollResultsButton.setVisibility(View.GONE);
if (poll.getShowOptionsUrl() != null) {
holder.hidePollResultsButton.setOnClickListener(v -> viewModel.loadUrl(poll.getShowOptionsUrl()));
holder.hidePollResultsButton.setVisibility(View.VISIBLE);
} else holder.hidePollResultsButton.setVisibility(View.GONE);
if (poll.getPollFormUrl() != null) {
holder.submitButton.setOnClickListener(v -> viewModel.submitVote(holder.optionsLayout));
holder.submitButton.setVisibility(View.VISIBLE);
} else holder.submitButton.setVisibility(View.GONE);
} else {
Post currentPost = (Post) topicItems.get(position);
if (currentHolder instanceof PostViewHolder) {
final PostViewHolder holder = (PostViewHolder) currentHolder;
//Post's WebView parameters
holder.post.setClickable(true);
holder.post.setWebViewClient(new LinkLauncher());
//Avoids errors about layout having 0 width/height
holder.thumbnail.setMinimumWidth(1);
holder.thumbnail.setMinimumHeight(1);
//Sets thumbnail size
holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE);
holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE);
//noinspection ConstantConditions
Picasso.with(context)
.load(currentPost.getThumbnailURL())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
//Sets username,submit date, index number, subject, post's and attached files texts
holder.username.setText(currentPost.getAuthor());
holder.postDate.setText(currentPost.getPostDate());
if (currentPost.getPostNumber() != 0)
holder.postNum.setText(context.getString(
R.string.user_number_of_posts, currentPost.getPostNumber()));
else
holder.postNum.setText("");
holder.subject.setText(currentPost.getSubject());
holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null);
if ((currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0)
|| (currentPost.getLastEdit() != null)) {
holder.bodyFooterDivider.setVisibility(View.VISIBLE);
holder.postFooter.removeAllViews();
if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) {
int filesTextColor;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
filesTextColor = context.getResources().getColor(R.color.accent, null);
} else //noinspection deprecation
filesTextColor = context.getResources().getColor(R.color.accent);
for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) {
final TextView attached = new TextView(context);
attached.setTextSize(10f);
attached.setClickable(true);
attached.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
attached.setText(faIconFromFilename(attachedFile.getFilename()) + " "
+ attachedFile.getFilename() + attachedFile.getFileInfo());
attached.setTextColor(filesTextColor);
attached.setPadding(0, 3, 0, 3);
attached.setOnClickListener(view -> ((BaseActivity) context).downloadFile(attachedFile));
holder.postFooter.addView(attached);
}
}
if (currentPost.getLastEdit() != null && currentPost.getLastEdit().length() > 0) {
int lastEditTextColor;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
lastEditTextColor = context.getResources().getColor(R.color.white, null);
} else //noinspection deprecation
lastEditTextColor = context.getResources().getColor(R.color.white);
final TextView lastEdit = new TextView(context);
lastEdit.setTextSize(12f);
lastEdit.setText(currentPost.getLastEdit());
lastEdit.setTextColor(lastEditTextColor);
lastEdit.setPadding(0, 3, 0, 3);
holder.postFooter.addView(lastEdit);
}
} else {
holder.bodyFooterDivider.setVisibility(View.GONE);
holder.postFooter.removeAllViews();
}
String mSpecialRank, mRank, mGender, mNumberOfPosts, mPersonalText; String mSpecialRank, mRank, mGender, mNumberOfPosts, mPersonalText;
int mNumberOfStars, mUserColor; int mNumberOfStars, mUserColor;
if (!currentPost.isDeleted()) { //Sets user's extra info if (!currentPost.isDeleted()) { //Sets user's extra info
mSpecialRank = currentPost.getSpecialRank(); mSpecialRank = currentPost.getSpecialRank();
mRank = currentPost.getRank(); mRank = currentPost.getRank();
mGender = currentPost.getGender(); mGender = currentPost.getGender();
mNumberOfPosts = currentPost.getNumberOfPosts(); mNumberOfPosts = currentPost.getNumberOfPosts();
mPersonalText = currentPost.getPersonalText(); mPersonalText = currentPost.getPersonalText();
mNumberOfStars = currentPost.getNumberOfStars(); mNumberOfStars = currentPost.getNumberOfStars();
} else { } else {
mSpecialRank = null; mSpecialRank = null;
mRank = null; mRank = null;
mGender = null; mGender = null;
mNumberOfPosts = null; mNumberOfPosts = null;
mPersonalText = null; mPersonalText = null;
mNumberOfStars = 0; mNumberOfStars = 0;
}
mUserColor = currentPost.getUserColor();
if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) {
holder.specialRank.setText(mSpecialRank);
holder.specialRank.setVisibility(View.VISIBLE);
} else
holder.specialRank.setVisibility(View.GONE);
if (!Objects.equals(mRank, "") && mRank != null) {
holder.rank.setText(mRank);
holder.rank.setVisibility(View.VISIBLE);
} else
holder.rank.setVisibility(View.GONE);
if (!Objects.equals(mGender, "") && mGender != null) {
holder.gender.setText(mGender);
holder.gender.setVisibility(View.VISIBLE);
} else
holder.gender.setVisibility(View.GONE);
if (!Objects.equals(mNumberOfPosts, "") && mNumberOfPosts != null) {
holder.numberOfPosts.setText(mNumberOfPosts);
holder.numberOfPosts.setVisibility(View.VISIBLE);
} else
holder.numberOfPosts.setVisibility(View.GONE);
if (!Objects.equals(mPersonalText, "") && mPersonalText != null) {
holder.personalText.setText("\"" + mPersonalText + "\"");
holder.personalText.setVisibility(View.VISIBLE);
} else
holder.personalText.setVisibility(View.GONE);
if (mUserColor != USER_COLOR_YELLOW) {
holder.username.setTextColor(mUserColor);
} else {
holder.username.setTextColor(USER_COLOR_WHITE);
}
if (mNumberOfStars > 0) {
holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
String aStar = context.getResources().getString(R.string.fa_icon_star);
StringBuilder usersStars = new StringBuilder();
for (int i = 0; i < mNumberOfStars; ++i) {
usersStars.append(aStar);
} }
holder.stars.setText(usersStars.toString()); mUserColor = currentPost.getUserColor();
holder.stars.setTextColor(mUserColor);
holder.stars.setVisibility(View.VISIBLE); if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) {
} else holder.specialRank.setText(mSpecialRank);
holder.stars.setVisibility(View.GONE); holder.specialRank.setVisibility(View.VISIBLE);
} else
if (currentPost.isUserMentionedInPost()) { holder.specialRank.setVisibility(View.GONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (!Objects.equals(mRank, "") && mRank != null) {
holder.cardChildLinear.setBackground(context.getResources(). holder.rank.setText(mRank);
getDrawable(R.drawable.mention_card, null)); holder.rank.setVisibility(View.VISIBLE);
} else //noinspection deprecation } else
holder.cardChildLinear.setBackground(context.getResources(). holder.rank.setVisibility(View.GONE);
getDrawable(R.drawable.mention_card)); if (!Objects.equals(mGender, "") && mGender != null) {
} else if (mUserColor == TopicParser.USER_COLOR_PINK) { holder.gender.setText(mGender);
//Special card for special member of the month! holder.gender.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { } else
holder.cardChildLinear.setBackground(context.getResources(). holder.gender.setVisibility(View.GONE);
getDrawable(R.drawable.member_of_the_month_card, null)); if (!Objects.equals(mNumberOfPosts, "") && mNumberOfPosts != null) {
} else //noinspection deprecation holder.numberOfPosts.setText(mNumberOfPosts);
holder.cardChildLinear.setBackground(context.getResources(). holder.numberOfPosts.setVisibility(View.VISIBLE);
getDrawable(R.drawable.member_of_the_month_card)); } else
} else holder.cardChildLinear.setBackground(null); holder.numberOfPosts.setVisibility(View.GONE);
if (!Objects.equals(mPersonalText, "") && mPersonalText != null) {
//Avoid's view's visibility recycling holder.personalText.setText("\"" + mPersonalText + "\"");
if (!currentPost.isDeleted() && viewModel.isUserExtraInfoVisible(holder.getAdapterPosition())) { holder.personalText.setVisibility(View.VISIBLE);
holder.userExtraInfo.setVisibility(View.VISIBLE); } else
holder.userExtraInfo.setAlpha(1.0f); holder.personalText.setVisibility(View.GONE);
if (mUserColor != USER_COLOR_YELLOW) {
holder.username.setMaxLines(Integer.MAX_VALUE); holder.username.setTextColor(mUserColor);
holder.username.setEllipsize(null); } else {
holder.username.setTextColor(USER_COLOR_WHITE);
holder.subject.setTextColor(Color.parseColor("#FFFFFF")); }
holder.subject.setMaxLines(Integer.MAX_VALUE); if (mNumberOfStars > 0) {
holder.subject.setEllipsize(null); holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets()
} else { , "fonts/fontawesome-webfont.ttf"));
holder.userExtraInfo.setVisibility(View.GONE);
holder.userExtraInfo.setAlpha(0.0f); String aStar = context.getResources().getString(R.string.fa_icon_star);
StringBuilder usersStars = new StringBuilder();
for (int i = 0; i < mNumberOfStars; ++i) {
usersStars.append(aStar);
}
holder.stars.setText(usersStars.toString());
holder.stars.setTextColor(mUserColor);
holder.stars.setVisibility(View.VISIBLE);
} else
holder.stars.setVisibility(View.GONE);
if (currentPost.isUserMentionedInPost()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.cardChildLinear.setBackground(context.getResources().
getDrawable(R.drawable.mention_card, null));
} else //noinspection deprecation
holder.cardChildLinear.setBackground(context.getResources().
getDrawable(R.drawable.mention_card));
} else if (mUserColor == TopicParser.USER_COLOR_PINK) {
//Special card for special member of the month!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.cardChildLinear.setBackground(context.getResources().
getDrawable(R.drawable.member_of_the_month_card, null));
} else //noinspection deprecation
holder.cardChildLinear.setBackground(context.getResources().
getDrawable(R.drawable.member_of_the_month_card));
} else holder.cardChildLinear.setBackground(null);
holder.username.setMaxLines(1); //Avoid's view's visibility recycling
holder.username.setEllipsize(TextUtils.TruncateAt.END); if (!currentPost.isDeleted() && viewModel.isUserExtraInfoVisible(holder.getAdapterPosition())) {
holder.userExtraInfo.setVisibility(View.VISIBLE);
holder.userExtraInfo.setAlpha(1.0f);
holder.subject.setTextColor(Color.parseColor("#757575")); holder.username.setMaxLines(Integer.MAX_VALUE);
holder.subject.setMaxLines(1); holder.username.setEllipsize(null);
holder.subject.setEllipsize(TextUtils.TruncateAt.END);
}
if (!currentPost.isDeleted()) {
//Sets graphics behavior
holder.thumbnail.setOnClickListener(view -> {
//Clicking the thumbnail opens user's profile
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
if (currentPost.getThumbnailURL() == null)
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailURL());
extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
holder.header.setOnClickListener(v -> {
//Clicking the header makes it expand/collapse
viewModel.toggleUserInfo(holder.getAdapterPosition());
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), holder.userExtraInfo);
});
//Clicking the expanded part of a header (the extra info) makes it collapse
holder.userExtraInfo.setOnClickListener(v -> {
viewModel.hideUserInfo(holder.getAdapterPosition());
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), (LinearLayout) v);
});
} else {
holder.header.setOnClickListener(null);
holder.userExtraInfo.setOnClickListener(null);
}
holder.overflowButton.setOnClickListener(view -> { holder.subject.setTextColor(Color.parseColor("#FFFFFF"));
//Inflates the popup menu content holder.subject.setMaxLines(Integer.MAX_VALUE);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); holder.subject.setEllipsize(null);
if (layoutInflater == null) { } else {
return; holder.userExtraInfo.setVisibility(View.GONE);
} holder.userExtraInfo.setAlpha(0.0f);
View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null);
//Creates the PopupWindow
final PopupWindow popUp = new PopupWindow(holder.overflowButton.getContext());
popUp.setContentView(popUpContent);
popUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
popUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
popUp.setFocusable(true);
TextView shareButton = popUpContent.findViewById(R.id.post_share_button);
Drawable shareStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_share_white_24dp);
shareButton.setCompoundDrawablesRelativeWithIntrinsicBounds(shareStartDrawable, null, null, null);
shareButton.setOnClickListener(v -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_TEXT, currentPost.getPostURL());
context.startActivity(Intent.createChooser(sendIntent, "Share via"));
popUp.dismiss();
});
final TextView editPostButton = popUpContent.findViewById(R.id.edit_post); holder.username.setMaxLines(1);
Drawable editStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_edit_white_24dp); holder.username.setEllipsize(TextUtils.TruncateAt.END);
editPostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(editStartDrawable, null, null, null);
if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) { holder.subject.setTextColor(Color.parseColor("#757575"));
editPostButton.setVisibility(View.GONE); holder.subject.setMaxLines(1);
} else { holder.subject.setEllipsize(TextUtils.TruncateAt.END);
editPostButton.setOnClickListener(v -> { }
viewModel.prepareForEdit(position, postsList.get(position).getPostEditURL()); if (!currentPost.isDeleted()) {
popUp.dismiss(); //Sets graphics behavior
holder.thumbnail.setOnClickListener(view -> {
//Clicking the thumbnail opens user's profile
Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
if (currentPost.getThumbnailURL() == null)
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailURL());
extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
holder.header.setOnClickListener(v -> {
//Clicking the header makes it expand/collapse
viewModel.toggleUserInfo(holder.getAdapterPosition());
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), holder.userExtraInfo);
}); });
//Clicking the expanded part of a header (the extra info) makes it collapse
holder.userExtraInfo.setOnClickListener(v -> {
viewModel.hideUserInfo(holder.getAdapterPosition());
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), (LinearLayout) v);
});
} else {
holder.header.setOnClickListener(null);
holder.userExtraInfo.setOnClickListener(null);
} }
TextView deletePostButton = popUpContent.findViewById(R.id.delete_post); holder.overflowButton.setOnClickListener(view -> {
//Inflates the popup menu content
if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) { LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
deletePostButton.setVisibility(View.GONE); if (layoutInflater == null) {
} else { return;
Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp); }
deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null); View popUpContent = layoutInflater.inflate(R.layout.activity_topic_overflow_menu, null);
popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> {
new AlertDialog.Builder(holder.overflowButton.getContext()) //Creates the PopupWindow
.setTitle("Delete post") final PopupWindow popUp = new PopupWindow(holder.overflowButton.getContext());
.setMessage("Do you really want to delete this post?") popUp.setContentView(popUpContent);
.setIcon(android.R.drawable.ic_dialog_alert) popUp.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> viewModel.deletePost(currentPost.getPostDeleteURL())) popUp.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
.setNegativeButton(android.R.string.no, null).show(); popUp.setFocusable(true);
TextView shareButton = popUpContent.findViewById(R.id.post_share_button);
Drawable shareStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_share_white_24dp);
shareButton.setCompoundDrawablesRelativeWithIntrinsicBounds(shareStartDrawable, null, null, null);
shareButton.setOnClickListener(v -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_TEXT, currentPost.getPostURL());
context.startActivity(Intent.createChooser(sendIntent, "Share via"));
popUp.dismiss(); popUp.dismiss();
}); });
}
//Displays the popup final TextView editPostButton = popUpContent.findViewById(R.id.edit_post);
popUp.showAsDropDown(holder.overflowButton); Drawable editStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_edit_white_24dp);
}); editPostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(editStartDrawable, null, null, null);
if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) {
editPostButton.setVisibility(View.GONE);
} else {
editPostButton.setOnClickListener(v -> {
viewModel.prepareForEdit(position, currentPost.getPostEditURL());
popUp.dismiss();
});
}
//noinspection PointlessBooleanExpression,ConstantConditions TextView deletePostButton = popUpContent.findViewById(R.id.delete_post);
if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) {
holder.quoteToggle.setVisibility(View.GONE); if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) {
} else { deletePostButton.setVisibility(View.GONE);
if (viewModel.getToQuoteList().contains(currentPost.getPostIndex())) } else {
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp); Drawable deleteStartDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_white_24dp);
else deletePostButton.setCompoundDrawablesRelativeWithIntrinsicBounds(deleteStartDrawable, null, null, null);
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_24dp); popUpContent.findViewById(R.id.delete_post).setOnClickListener(v -> {
//Sets graphics behavior new AlertDialog.Builder(holder.overflowButton.getContext())
holder.quoteToggle.setOnClickListener(view -> { .setTitle("Delete post")
viewModel.postIndexToggle(currentPost.getPostIndex()); .setMessage("Do you really want to delete this post?")
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> viewModel.deletePost(currentPost.getPostDeleteURL()))
.setNegativeButton(android.R.string.no, null).show();
popUp.dismiss();
});
}
//Displays the popup
popUp.showAsDropDown(holder.overflowButton);
});
//noinspection PointlessBooleanExpression,ConstantConditions
if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) {
holder.quoteToggle.setVisibility(View.GONE);
} else {
if (viewModel.getToQuoteList().contains(currentPost.getPostIndex())) if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp); holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
else else
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_24dp); holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_24dp);
}); //Sets graphics behavior
} holder.quoteToggle.setOnClickListener(view -> {
} else if (currentHolder instanceof QuickReplyViewHolder) { viewModel.postIndexToggle(currentPost.getPostIndex());
final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder; if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
//noinspection ConstantConditions else
Picasso.with(context) holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_24dp);
.load(getSessionManager().getAvatarLink()) });
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue());
holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
holder.replyEditor.setEmojiKeyboardOwner(emojiKeyboardOwner);
InputConnection ic = holder.replyEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic);
holder.replyEditor.updateEmojiKeyboardVisibility();
holder.replyEditor.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
InputConnection ic12 = holder.replyEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic12);
holder.replyEditor.updateEmojiKeyboardVisibility();
});
holder.replyEditor.setText(viewModel.getBuildedQuotes());
holder.replyEditor.setOnSubmitListener(view -> {
if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.replyEditor.getText().toString().isEmpty()) {
holder.replyEditor.setError("Required");
return;
}
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false);
emojiKeyboardOwner.setEmojiKeyboardVisible(false);
viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
holder.replyEditor.getText().toString());
});
holder.replyEditor.setOnClickListener(view -> holder.replyEditor.setError(null));
if (backPressHidden) {
holder.replyEditor.requestFocus();
backPressHidden = false;
}
} else if (currentHolder instanceof EditMessageViewHolder) {
final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder;
//noinspection ConstantConditions
Picasso.with(context)
.load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.editSubject.setText(postsList.get(position).getSubject());
holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.editSubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
holder.editEditor.setEmojiKeyboardOwner(emojiKeyboardOwner);
InputConnection ic = holder.editEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic);
holder.editEditor.updateEmojiKeyboardVisibility();
holder.editEditor.setText(viewModel.getPostBeingEditedText());
holder.editEditor.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
InputConnection ic1 = holder.editEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic1);
holder.editEditor.updateEmojiKeyboardVisibility();
} }
}); } else if (currentHolder instanceof QuickReplyViewHolder) {
holder.editEditor.setOnSubmitListener(view -> { final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder;
if (holder.editSubject.getText().toString().isEmpty()) return;
if (holder.editEditor.getText().toString().isEmpty()) { //noinspection ConstantConditions
holder.editEditor.setError("Required"); Picasso.with(context)
return; .load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue());
holder.quickReplySubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.quickReplySubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
holder.replyEditor.setEmojiKeyboardOwner(emojiKeyboardOwner);
InputConnection ic = holder.replyEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic);
holder.replyEditor.updateEmojiKeyboardVisibility();
holder.replyEditor.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
InputConnection ic12 = holder.replyEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic12);
holder.replyEditor.updateEmojiKeyboardVisibility();
});
holder.replyEditor.setText(viewModel.getBuildedQuotes());
holder.replyEditor.setOnSubmitListener(view -> {
if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.replyEditor.getText().toString().isEmpty()) {
holder.replyEditor.setError("Required");
return;
}
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false);
emojiKeyboardOwner.setEmojiKeyboardVisible(false);
viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
holder.replyEditor.getText().toString());
});
holder.replyEditor.setOnClickListener(view -> holder.replyEditor.setError(null));
if (backPressHidden) {
holder.replyEditor.requestFocus();
backPressHidden = false;
} }
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); } else if (currentHolder instanceof EditMessageViewHolder) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder;
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false); //noinspection ConstantConditions
emojiKeyboardOwner.setEmojiKeyboardVisible(false); Picasso.with(context)
.load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail_white_24dp, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.editSubject.setText(currentPost.getSubject());
holder.editSubject.setRawInputType(InputType.TYPE_CLASS_TEXT);
holder.editSubject.setImeOptions(EditorInfo.IME_ACTION_DONE);
holder.editEditor.setEmojiKeyboardOwner(emojiKeyboardOwner);
InputConnection ic = holder.editEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic);
holder.editEditor.updateEmojiKeyboardVisibility();
holder.editEditor.setText(viewModel.getPostBeingEditedText());
holder.editEditor.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
InputConnection ic1 = holder.editEditor.getInputConnection();
emojiKeyboardOwner.setEmojiKeyboardInputConnection(ic1);
holder.editEditor.updateEmojiKeyboardVisibility();
}
});
holder.editEditor.setOnSubmitListener(view -> {
if (holder.editSubject.getText().toString().isEmpty()) return;
if (holder.editEditor.getText().toString().isEmpty()) {
holder.editEditor.setError("Required");
return;
}
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false);
emojiKeyboardOwner.setEmojiKeyboardVisible(false);
viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editEditor.getText().toString()); viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editEditor.getText().toString());
}); });
if (backPressHidden) { if (backPressHidden) {
holder.editEditor.requestFocus(); holder.editEditor.requestFocus();
backPressHidden = false; backPressHidden = false;
}
} }
} }
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return postsList.size(); return topicItems.size();
} }
/** /**
@ -637,6 +738,27 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
} }
static class PollViewHolder extends RecyclerView.ViewHolder {
final TextView question, errorTooManySelected;
final LinearLayout optionsLayout;
final AppCompatButton submitButton;
final AppCompatButton removeVotesButton, showPollResultsButton, hidePollResultsButton;
final HorizontalBarChart voteChart;
public PollViewHolder(View itemView) {
super(itemView);
question = itemView.findViewById(R.id.question_textview);
optionsLayout = itemView.findViewById(R.id.options_layout);
submitButton = itemView.findViewById(R.id.submit_button);
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);
voteChart = itemView.findViewById(R.id.vote_chart);
}
}
/** /**
* This class is used to handle link clicks in WebViews. When link url is one that the app can * This class is used to handle link clicks in WebViews. When link url is one that the app can
* handle internally, it does. Otherwise user is prompt to open the link in a browser. * handle internally, it does. Otherwise user is prompt to open the link in a browser.
@ -680,8 +802,8 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (tmpUrlSbstr.contains("msg")) if (tmpUrlSbstr.contains("msg"))
tmpUrlSbstr = tmpUrlSbstr.substring(0, tmpUrlSbstr.indexOf("msg") - 1); tmpUrlSbstr = tmpUrlSbstr.substring(0, tmpUrlSbstr.indexOf("msg") - 1);
int testAgainst = Integer.parseInt(tmpUrlSbstr); int testAgainst = Integer.parseInt(tmpUrlSbstr);
for (int i = 0; i < postsList.size(); i++) { for (int i = 0; i < topicItems.size(); i++) {
if (postsList.get(i).getPostIndex() == testAgainst) { if (topicItems.get(i) instanceof Post && ((Post) topicItems.get(i)).getPostIndex() == testAgainst) {
//same page //same page
Timber.e(Integer.toString(i)); Timber.e(Integer.toString(i));
postFocusListener.onPostFocusChange(i); postFocusListener.onPostFocusChange(i);

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.activities.topic; package gr.thmmy.mthmmy.activities.topic;
import android.graphics.Color; import android.graphics.Color;
import android.util.Log;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@ -13,11 +14,14 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Poll;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber; import timber.log.Timber;
@ -146,10 +150,16 @@ public class TopicParser {
* @return {@link ArrayList} of {@link Post}s * @return {@link ArrayList} of {@link Post}s
* @see org.jsoup.Jsoup Jsoup * @see org.jsoup.Jsoup Jsoup
*/ */
public static ArrayList<Post> parseTopic(Document topic, ParseHelpers.Language language) { public static ArrayList<TopicItem> parseTopic(Document topic, ParseHelpers.Language language) {
//Method's variables //Method's variables
final int NO_INDEX = -1; final int NO_INDEX = -1;
ArrayList<Post> parsedPostsList = new ArrayList<>();
ArrayList<TopicItem> parsedPostsList = new ArrayList<>();
Poll poll = findPoll(topic);
if (poll != null)
parsedPostsList.add(poll);
Elements postRows; Elements postRows;
//Each row is a post //Each row is a post
@ -468,6 +478,88 @@ public class TopicParser {
return parsedPostsList; return parsedPostsList;
} }
private static Poll findPoll(Document topic) {
Pattern integerPattern = Pattern.compile("[0-9]+");
Elements tables = topic.select("table");
for (int i = 0; i < tables.size(); i++) {
try {
Element image = tables.get(i).child(0).child(0).child(0);
if (image.html().contains("Poll") || image.html().contains("Ψηφοφορία")) {
// has poll in english
String question;
ArrayList<Poll.Entry> entries = new ArrayList<>();
int availableVoteCount = 0;
String pollFormUrl = null, sc = null, removeVoteUrl = null, showVoteResultsUrl = null,
showOptionsUrl = null;
Element secondRow = tables.get(i).select("tr[class=windowbg]").first();
Element secondColumn = secondRow.child(1);
String columnString = secondColumn.outerHtml();
question = columnString.substring(columnString.indexOf('>') + 1, columnString.indexOf('<', 2)).trim();
Element form = secondColumn.select("form").first();
if (form != null) {
// english poll in vote mode
pollFormUrl = form.attr("action");
sc = form.select("input[name=sc]").first().attr("value");
int rowIndex = -1;
Elements possibleEntriesRows = form.child(0).child(0).children();
for (int j = 0; j < possibleEntriesRows.size(); j++) {
if (possibleEntriesRows.get(j).select("input").size() > 0) {
rowIndex = j;
break;
}
}
String entriesRaw = form.child(0).child(0).child(rowIndex).child(0).html();
Matcher entryMatcher = Pattern.compile(">[^<]+<br").matcher(entriesRaw);
while (entryMatcher.find()) {
entries.add(new Poll.Entry(entriesRaw.substring(entryMatcher.start() + 1, entryMatcher.end() - 3).trim()));
}
Element promptColumn = form.child(0).child(0).child(0);
String prompt = promptColumn.text();
Matcher integerMatcher = integerPattern.matcher(prompt);
if (integerMatcher.find())
availableVoteCount = Integer.parseInt(prompt.substring(integerMatcher.start(), integerMatcher.end()));
else
availableVoteCount = 1;
Elements links = form.select("a");
if (links != null && links.size() > 0) {
showVoteResultsUrl = links.first().attr("href");
}
} else {
// english poll in results mode
Elements optionRows = secondColumn.child(0).child(0).select("table").first().child(0).children();
for (int j = 0; j < optionRows.size(); j++) {
String optionName = optionRows.get(j).child(0).text();
String voteCountDescription = optionRows.get(j).child(1).text();
Matcher integerMatcher = integerPattern.matcher(voteCountDescription);
integerMatcher.find();
int voteCount = Integer.parseInt(voteCountDescription.substring(integerMatcher.start(),
integerMatcher.end()));
entries.add(new Poll.Entry(optionName, voteCount));
}
Elements links = secondColumn.select("a");
if (links != null && links.size() > 0) {
if (links.first().text().equals("Remove Vote") || links.first().text().equals("Αφαίρεση ψήφου"))
removeVoteUrl = links.first().attr("href");
else if (links.first().text().equals("Voting options") || links.first().text().equals("Επιλογές ψηφοφορίας"))
showOptionsUrl = links.first().attr("href");
}
}
return new Poll(question, entries.toArray(new Poll.Entry[0]), availableVoteCount,
pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl);
}
} catch (Exception e) {
Timber.v(e, "Could not parse a poll");
}
}
return null;
}
/** /**
* Returns the color of a user according to user's rank on forum. * Returns the color of a user according to user's rank on forum.
* *

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

@ -12,8 +12,8 @@ import okhttp3.Response;
public class DeleteTask extends NetworkTask<Void> { public class DeleteTask extends NetworkTask<Void> {
public DeleteTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskFinishedListener<Void> onParseTaskFinishedListener) { public DeleteTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<Void> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskFinishedListener); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
@Override @Override
@ -28,7 +28,7 @@ public class DeleteTask extends NetworkTask<Void> {
} }
@Override @Override
protected Void performTask(Document document) { protected Void performTask(Document document, Response response) {
return null; return null;
} }

20
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/RemoveVoteTask.java

@ -0,0 +1,20 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import org.jsoup.nodes.Document;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.Response;
public class RemoveVoteTask extends NetworkTask<Void> {
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

48
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/SubmitVoteTask.java

@ -0,0 +1,48 @@
package gr.thmmy.mthmmy.activities.topic.tasks;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.utils.NetworkResultCodes;
import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class SubmitVoteTask extends NetworkTask<Void> {
private int[] votes;
public SubmitVoteTask(int... votes) {
this.votes = votes;
}
@Override
protected Response sendRequest(OkHttpClient client, String... input) throws IOException {
MultipartBody.Builder postBodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("sc", input[1]);
for (int vote : votes) {
postBodyBuilder.addFormDataPart("options[]", Integer.toString(vote));
}
Request voteRequest = new Request.Builder()
.url(input[0])
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBodyBuilder.build())
.build();
return client.newCall(voteRequest).execute();
}
@Override
protected Void performTask(Document document, Response response) {
return null;
}
@Override
protected int getResultCode(Response response, Void data) {
return NetworkResultCodes.SUCCESSFUL;
}
}

5
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java

@ -13,6 +13,7 @@ import gr.thmmy.mthmmy.activities.topic.TopicParser;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -92,14 +93,14 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
//Finds number of pages //Finds number of pages
int pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language); int pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language);
ArrayList<Post> newPostsList = TopicParser.parseTopic(topic, language); ArrayList<TopicItem> newPostsList = TopicParser.parseTopic(topic, language);
int loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(newPageUrl)); int loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(newPageUrl));
//Finds the position of the focused message if present //Finds the position of the focused message if present
int focusedPostIndex = 0; int focusedPostIndex = 0;
for (int i = 0; i < newPostsList.size(); ++i) { for (int i = 0; i < newPostsList.size(); ++i) {
if (newPostsList.get(i).getPostIndex() == postFocus) { if (newPostsList.get(i) instanceof Post && ((Post) newPostsList.get(i)).getPostIndex() == postFocus) {
focusedPostIndex = i; focusedPostIndex = i;
break; break;
} }

7
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java

@ -3,6 +3,7 @@ package gr.thmmy.mthmmy.activities.topic.tasks;
import java.util.ArrayList; import java.util.ArrayList;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.TopicItem;
public class TopicTaskResult { public class TopicTaskResult {
private final TopicTask.ResultCode resultCode; private final TopicTask.ResultCode resultCode;
@ -16,7 +17,7 @@ public class TopicTaskResult {
* This topic's reply url * This topic's reply url
*/ */
private final String replyPageUrl; private final String replyPageUrl;
private final ArrayList<Post> newPostsList; private final ArrayList<TopicItem> newPostsList;
/** /**
* The topicId of the loaded page * The topicId of the loaded page
*/ */
@ -38,7 +39,7 @@ public class TopicTaskResult {
private final String topicViewers; private final String topicViewers;
public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle, public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle,
String replyPageUrl, ArrayList<Post> newPostsList, int loadedPageTopicId, String replyPageUrl, ArrayList<TopicItem> newPostsList, int loadedPageTopicId,
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods, int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
String topicViewers) { String topicViewers) {
this.resultCode = resultCode; this.resultCode = resultCode;
@ -65,7 +66,7 @@ public class TopicTaskResult {
return replyPageUrl; return replyPageUrl;
} }
public ArrayList<Post> getNewPostsList() { public ArrayList<TopicItem> getNewPostsList() {
return newPostsList; return newPostsList;
} }

108
app/src/main/java/gr/thmmy/mthmmy/model/Poll.java

@ -0,0 +1,108 @@
package gr.thmmy.mthmmy.model;
import java.text.DecimalFormat;
public class Poll extends TopicItem {
public static final int TYPE_POLL = 3;
private final String question;
private Entry[] entries;
private int availableVoteCount;
private String pollFormUrl, sc, removeVoteUrl, showVoteResultsUrl, showOptionsUrl;
public Poll(String question, Entry[] entries, int availableVoteCount, String pollFormUrl, String sc,
String removeVoteUrl, String showVoteResultsUrl, String showOptionsUrl) {
this.question = question;
this.entries = entries;
this.availableVoteCount = availableVoteCount;
this.pollFormUrl = pollFormUrl;
this.sc = sc;
this.removeVoteUrl = removeVoteUrl;
this.showVoteResultsUrl = showVoteResultsUrl;
this.showOptionsUrl = showOptionsUrl;
}
public int totalVotes() {
int sum = 0;
for (Entry entry : entries) {
sum += entry.votes;
}
return sum;
}
public String getVotePercentage(int index) {
DecimalFormat format = new DecimalFormat(".#");
double percentage = 100 * ((double) entries[index].votes / (double) totalVotes());
return format.format(percentage);
}
public String getQuestion() {
return question;
}
public Entry[] getEntries() {
return entries;
}
public int getAvailableVoteCount() {
return availableVoteCount;
}
public String getPollFormUrl() {
return pollFormUrl;
}
public String getSc() {
return sc;
}
public String getRemoveVoteUrl() {
return removeVoteUrl;
}
public String getShowVoteResultsUrl() {
return showVoteResultsUrl;
}
public String getShowOptionsUrl() {
return showOptionsUrl;
}
public static class Entry {
private final String entryName;
private int votes;
public Entry(String entryName, int votes) {
this.entryName = entryName;
this.votes = votes;
}
/**
* Constructor for entry with unknown number of votes
*
* @param entryName
* The name of the entry
*/
public Entry(String entryName) {
this.entryName = entryName;
votes = -1;
}
public String getEntryName() {
return entryName;
}
public int getVotes() {
return votes;
}
public void setVotes(int votes) {
this.votes = votes;
}
@Override
public String toString() {
return "Vote label:" + entryName + ", num votes:" + votes;
}
}
}

2
app/src/main/java/gr/thmmy/mthmmy/model/Post.java

@ -15,7 +15,7 @@ import java.util.Objects;
* gender, number of posts, personal text and number of start to be described <b>in addition to * gender, number of posts, personal text and number of start to be described <b>in addition to
* previous fields</b>.</p> * previous fields</b>.</p>
*/ */
public class Post { public class Post extends TopicItem {
public static final int TYPE_POST = 0; public static final int TYPE_POST = 0;
public static final int TYPE_QUICK_REPLY = 1; public static final int TYPE_QUICK_REPLY = 1;
public static final int TYPE_EDIT = 2; public static final int TYPE_EDIT = 2;

10
app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java

@ -3,6 +3,8 @@ package gr.thmmy.mthmmy.model;
import android.net.Uri; import android.net.Uri;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import timber.log.Timber; import timber.log.Timber;
@ -180,10 +182,10 @@ public class ThmmyPage {
public static String getTopicId(String topicUrl) { public static String getTopicId(String topicUrl) {
if (resolvePageCategory(Uri.parse(topicUrl)) == PageCategory.TOPIC) { if (resolvePageCategory(Uri.parse(topicUrl)) == PageCategory.TOPIC) {
String returnString = topicUrl.substring(topicUrl.indexOf("topic=") + 6); Matcher topicIdMatcher = Pattern.compile("topic=[0-9]+").matcher(topicUrl);
if (returnString.contains(".")) if (topicIdMatcher.find()) {
returnString = returnString.substring(0, returnString.indexOf(".")); return topicUrl.substring(topicIdMatcher.start() + 6, topicIdMatcher.end());
return returnString; } else return null;
} }
return null; return null;
} }

5
app/src/main/java/gr/thmmy/mthmmy/model/TopicItem.java

@ -0,0 +1,5 @@
package gr.thmmy.mthmmy.model;
public abstract class TopicItem {
}

62
app/src/main/java/gr/thmmy/mthmmy/utils/ExternalAsyncTask.java

@ -4,77 +4,77 @@ import android.os.AsyncTask;
public abstract class ExternalAsyncTask<U, V> extends AsyncTask<U, Void, V> { public abstract class ExternalAsyncTask<U, V> extends AsyncTask<U, Void, V> {
protected OnParseTaskStartedListener onParseTaskStartedListener; protected OnTaskStartedListener onTaskStartedListener;
protected OnParseTaskCancelledListener onParseTaskCancelledListener; protected OnTaskCancelledListener onTaskCancelledListener;
protected OnParseTaskFinishedListener<V> onParseTaskFinishedListener; protected OnTaskFinishedListener<V> onTaskFinishedListener;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
if (onParseTaskStartedListener != null) if (onTaskStartedListener != null)
onParseTaskStartedListener.onParseStart(); onTaskStartedListener.onTaskStarted();
else else
super.onPreExecute(); super.onPreExecute();
} }
@Override @Override
protected void onCancelled() { protected void onCancelled() {
if (onParseTaskCancelledListener != null) if (onTaskCancelledListener != null)
onParseTaskCancelledListener.onParseCancel(); onTaskCancelledListener.onTaskCanceled();
else else
super.onCancelled(); super.onCancelled();
} }
@Override @Override
protected void onCancelled(V v) { protected void onCancelled(V v) {
if (onParseTaskCancelledListener != null) if (onTaskCancelledListener != null)
onParseTaskCancelledListener.onParseCancel(); onTaskCancelledListener.onTaskCanceled();
else else
super.onCancelled(); super.onCancelled();
} }
@Override @Override
protected void onPostExecute(V v) { protected void onPostExecute(V v) {
if (onParseTaskFinishedListener != null) if (onTaskFinishedListener != null)
onParseTaskFinishedListener.onParseFinish(v); onTaskFinishedListener.onTaskFinished(v);
else else
super.onPostExecute(v); super.onPostExecute(v);
} }
public ExternalAsyncTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskCancelledListener onParseTaskCancelledListener, public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnParseTaskFinishedListener<V> onParseTaskFinishedListener) { OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onParseTaskStartedListener = onParseTaskStartedListener; this.onTaskStartedListener = onTaskStartedListener;
this.onParseTaskCancelledListener = onParseTaskCancelledListener; this.onTaskCancelledListener = onTaskCancelledListener;
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onTaskFinishedListener = onTaskFinishedListener;
} }
public ExternalAsyncTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskFinishedListener<V> onParseTaskFinishedListener) { public ExternalAsyncTask(OnTaskStartedListener onTaskStartedListener, OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onParseTaskStartedListener = onParseTaskStartedListener; this.onTaskStartedListener = onTaskStartedListener;
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onTaskFinishedListener = onTaskFinishedListener;
} }
public ExternalAsyncTask() { } public ExternalAsyncTask() { }
public void setOnParseTaskStartedListener(OnParseTaskStartedListener onParseTaskStartedListener) { public void setOnTaskStartedListener(OnTaskStartedListener onTaskStartedListener) {
this.onParseTaskStartedListener = onParseTaskStartedListener; this.onTaskStartedListener = onTaskStartedListener;
} }
public void setOnParseTaskCancelledListener(OnParseTaskCancelledListener onParseTaskCancelledListener) { public void setOnTaskCancelledListener(OnTaskCancelledListener onTaskCancelledListener) {
this.onParseTaskCancelledListener = onParseTaskCancelledListener; this.onTaskCancelledListener = onTaskCancelledListener;
} }
public void setOnParseTaskFinishedListener(OnParseTaskFinishedListener<V> onParseTaskFinishedListener) { public void setOnTaskFinishedListener(OnTaskFinishedListener<V> onTaskFinishedListener) {
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onTaskFinishedListener = onTaskFinishedListener;
} }
public interface OnParseTaskStartedListener { public interface OnTaskStartedListener {
void onParseStart(); void onTaskStarted();
} }
public interface OnParseTaskCancelledListener { public interface OnTaskCancelledListener {
void onParseCancel(); void onTaskCanceled();
} }
public interface OnParseTaskFinishedListener<V> { public interface OnTaskFinishedListener<V> {
void onParseFinish(V result); void onTaskFinished(V result);
} }
} }

32
app/src/main/java/gr/thmmy/mthmmy/utils/NetworkTask.java

@ -14,17 +14,17 @@ import timber.log.Timber;
public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>> { public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>> {
protected OnParseTaskFinishedListener<T> onParseTaskFinishedListener; protected OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener;
public NetworkTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskCancelledListener onParseTaskCancelledListener, public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnParseTaskFinishedListener<T> onParseTaskFinishedListener) { OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskCancelledListener, null); super(onTaskStartedListener, onTaskCancelledListener, null);
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
} }
public NetworkTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskFinishedListener<T> onParseTaskFinishedListener) { public NetworkTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
super(onParseTaskStartedListener, null); super(onTaskStartedListener, null);
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
} }
public NetworkTask() {} public NetworkTask() {}
@ -49,7 +49,7 @@ public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>
return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null); return new Parcel<>(NetworkResultCodes.NETWORK_ERROR, null);
} }
try { try {
T data = performTask(Jsoup.parse(responseBodyString)); T data = performTask(Jsoup.parse(responseBodyString), response);
int resultCode = getResultCode(response, data); int resultCode = getResultCode(response, data);
return new Parcel<>(resultCode, data); return new Parcel<>(resultCode, data);
} catch (ParseException pe) { } catch (ParseException pe) {
@ -63,8 +63,8 @@ public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>
@Override @Override
protected void onPostExecute(Parcel<T> tParcel) { protected void onPostExecute(Parcel<T> tParcel) {
if (onParseTaskFinishedListener != null) if (onNetworkTaskFinishedListener != null)
onParseTaskFinishedListener.onParseFinish(tParcel.getResultCode(), tParcel.getData()); onNetworkTaskFinishedListener.onNetworkTaskFinished(tParcel.getResultCode(), tParcel.getData());
else else
super.onPostExecute(tParcel); super.onPostExecute(tParcel);
} }
@ -77,15 +77,15 @@ public abstract class NetworkTask<T> extends ExternalAsyncTask<String, Parcel<T>
return client.newCall(request).execute(); return client.newCall(request).execute();
} }
protected abstract T performTask(Document document); protected abstract T performTask(Document document, Response response);
protected abstract int getResultCode(Response response, T data); protected abstract int getResultCode(Response response, T data);
public void setOnParseTaskFinishedListener(OnParseTaskFinishedListener<T> onParseTaskFinishedListener) { public void setOnNetworkTaskFinishedListener(OnNetworkTaskFinishedListener<T> onNetworkTaskFinishedListener) {
this.onParseTaskFinishedListener = onParseTaskFinishedListener; this.onNetworkTaskFinishedListener = onNetworkTaskFinishedListener;
} }
public interface OnParseTaskFinishedListener<T> { public interface OnNetworkTaskFinishedListener<T> {
void onParseFinish(int resultCode, T data); void onNetworkTaskFinished(int resultCode, T data);
} }
} }

17
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/NewParseTask.java

@ -3,28 +3,29 @@ package gr.thmmy.mthmmy.utils.parsing;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import gr.thmmy.mthmmy.utils.NetworkTask; import gr.thmmy.mthmmy.utils.NetworkTask;
import okhttp3.Response;
public abstract class NewParseTask<T> extends NetworkTask<T> { public abstract class NewParseTask<T> extends NetworkTask<T> {
public NewParseTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskCancelledListener onParseTaskCancelledListener, public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnTaskCancelledListener onTaskCancelledListener,
OnParseTaskFinishedListener<T> onParseTaskFinishedListener) { OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskCancelledListener, onParseTaskFinishedListener); super(onTaskStartedListener, onTaskCancelledListener, onParseTaskFinishedListener);
} }
public NewParseTask(OnParseTaskStartedListener onParseTaskStartedListener, OnParseTaskFinishedListener<T> onParseTaskFinishedListener) { public NewParseTask(OnTaskStartedListener onTaskStartedListener, OnNetworkTaskFinishedListener<T> onParseTaskFinishedListener) {
super(onParseTaskStartedListener, onParseTaskFinishedListener); super(onTaskStartedListener, onParseTaskFinishedListener);
} }
public NewParseTask() {} public NewParseTask() {}
@Override @Override
protected final T performTask(Document document) { protected final T performTask(Document document, Response response) {
try { try {
return parse(document); return parse(document, response);
} catch (Exception e) { } catch (Exception e) {
throw new ParseException("Parse failed.", e); throw new ParseException("Parse failed.", e);
} }
} }
protected abstract T parse (Document document) throws ParseException; protected abstract T parse (Document document, Response response) throws ParseException;
} }

13
app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java

@ -5,6 +5,8 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import timber.log.Timber; import timber.log.Timber;
@ -179,11 +181,10 @@ public class ParseHelpers {
* @return the base URL of the given topic * @return the base URL of the given topic
*/ */
public static String getBaseURL(String topicURL) { public static String getBaseURL(String topicURL) {
if (topicURL.substring(0, topicURL.lastIndexOf(".")).contains("topic=")) String forumUrl = "https://www.thmmy.gr/smf/index.php?";
return topicURL.substring(0, topicURL.lastIndexOf(".")); Matcher baseUrlMatcher = Pattern.compile("topic=[0-9]+").matcher(topicURL);
else { if (baseUrlMatcher.find())
Timber.wtf(new ParseException("Could not parse base URL of topic")); return forumUrl + topicURL.substring(baseUrlMatcher.start(), baseUrlMatcher.end());
return ""; else return "";
}
} }
} }

97
app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java

@ -5,6 +5,9 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import java.util.ArrayList; import java.util.ArrayList;
@ -15,12 +18,18 @@ import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditResult;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply;
import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyResult; import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyResult;
import gr.thmmy.mthmmy.activities.topic.tasks.RemoveVoteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask; import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask;
import gr.thmmy.mthmmy.activities.topic.tasks.SubmitVoteTask;
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask; import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask;
import gr.thmmy.mthmmy.activities.topic.tasks.TopicTaskResult; import gr.thmmy.mthmmy.activities.topic.tasks.TopicTaskResult;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Poll;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.TopicItem;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.ExternalAsyncTask;
import gr.thmmy.mthmmy.utils.NetworkTask;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import timber.log.Timber; import timber.log.Timber;
@ -50,12 +59,16 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
//callbacks for topic activity //callbacks for topic activity
private TopicTask.TopicTaskObserver topicTaskObserver; private TopicTask.TopicTaskObserver topicTaskObserver;
private DeleteTask.OnParseTaskStartedListener deleteTaskStartedListener; private ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener;
private DeleteTask.OnParseTaskFinishedListener<Void> deleteTaskFinishedListener; private NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener;
private ReplyTask.ReplyTaskCallbacks replyFinishListener; private ReplyTask.ReplyTaskCallbacks replyFinishListener;
private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks; private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks;
private EditTask.EditTaskCallbacks editTaskCallbacks; private EditTask.EditTaskCallbacks editTaskCallbacks;
private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks; private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks;
private ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener;
private NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener;
private ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener;
private NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener;
/** /**
* Holds the value (index) of the page to be requested when a user interaction with bottom * Holds the value (index) of the page to be requested when a user interaction with bottom
@ -66,7 +79,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
private MutableLiveData<String> replyPageUrl = new MutableLiveData<>(); private MutableLiveData<String> replyPageUrl = new MutableLiveData<>();
private MutableLiveData<Integer> pageTopicId = new MutableLiveData<>(); private MutableLiveData<Integer> pageTopicId = new MutableLiveData<>();
private MutableLiveData<String> topicTitle = new MutableLiveData<>(); private MutableLiveData<String> topicTitle = new MutableLiveData<>();
private MutableLiveData<ArrayList<Post>> postsList = new MutableLiveData<>(); private MutableLiveData<ArrayList<TopicItem>> topicItems = new MutableLiveData<>();
private MutableLiveData<Integer> focusedPostIndex = new MutableLiveData<>(); private MutableLiveData<Integer> focusedPostIndex = new MutableLiveData<>();
private MutableLiveData<TopicTask.ResultCode> topicTaskResultCode = new MutableLiveData<>(); private MutableLiveData<TopicTask.ResultCode> topicTaskResultCode = new MutableLiveData<>();
private MutableLiveData<String> topicTreeAndMods = new MutableLiveData<>(); private MutableLiveData<String> topicTreeAndMods = new MutableLiveData<>();
@ -91,7 +104,17 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
loadUrl(topicUrl); loadUrl(topicUrl);
} }
public void performPageChange() { /**
* In contrasto to {@link TopicViewModel#reloadPage()} this method gets rid of any arguements
* in the url before refreshing
*/
public void resetPage() {
if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
Timber.i("Reseting page");
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(currentPageIndex * 15));
}
public void loadPageIndicated() {
if (pageIndicatorIndex.getValue() == null) if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
int pageRequested = pageIndicatorIndex.getValue() - 1; int pageRequested = pageIndicatorIndex.getValue() - 1;
@ -104,6 +127,35 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
} }
public void submitVote(LinearLayout optionsLayout) {
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!");
ArrayList<Integer> votes = new ArrayList<>();
if (optionsLayout.getChildAt(0) instanceof RadioGroup) {
RadioGroup optionsRadioGroup = (RadioGroup) optionsLayout.getChildAt(0);
votes.add(optionsRadioGroup.getCheckedRadioButtonId());
} else if (optionsLayout.getChildAt(0) instanceof CheckBox) {
for (int i = 0; i < optionsLayout.getChildCount(); i++) {
if (((CheckBox) optionsLayout.getChildAt(i)).isChecked())
votes.add(i);
}
}
int[] votesArray = new int[votes.size()];
for (int i = 0; i < votes.size(); i++) votesArray[i] = votes.get(i);
Poll poll = (Poll) topicItems.getValue().get(0);
SubmitVoteTask submitVoteTask = new SubmitVoteTask(votesArray);
submitVoteTask.setOnTaskStartedListener(voteTaskStartedListener);
submitVoteTask.setOnNetworkTaskFinishedListener(voteTaskFinishedListener);
submitVoteTask.execute(poll.getPollFormUrl(), poll.getSc());
}
public void removeVote() {
if (topicItems.getValue() == null) throw new NullPointerException("Topic task has not finished yet!");
RemoveVoteTask removeVoteTask = new RemoveVoteTask();
removeVoteTask.setOnTaskStartedListener(removeVoteTaskStartedListener);
removeVoteTask.setOnNetworkTaskFinishedListener(removeVoteTaskFinishedListener);
removeVoteTask.execute(((Poll) topicItems.getValue().get(0)).getRemoveVoteUrl());
}
public void prepareForReply() { public void prepareForReply() {
if (replyPageUrl.getValue() == null) if (replyPageUrl.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!"); throw new NullPointerException("Topic task has not finished yet!");
@ -194,7 +246,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
replyPageUrl.setValue(result.getReplyPageUrl()); replyPageUrl.setValue(result.getReplyPageUrl());
topicTitle.setValue(result.getTopicTitle()); topicTitle.setValue(result.getTopicTitle());
pageIndicatorIndex.setValue(result.getCurrentPageIndex()); pageIndicatorIndex.setValue(result.getCurrentPageIndex());
postsList.setValue(result.getNewPostsList()); topicItems.setValue(result.getNewPostsList());
focusedPostIndex.setValue(result.getFocusedPostIndex()); focusedPostIndex.setValue(result.getFocusedPostIndex());
isUserExtraInfoVisibile.clear(); isUserExtraInfoVisibile.clear();
for (int i = 0; i < result.getNewPostsList().size(); i++) { for (int i = 0; i < result.getNewPostsList().size(); i++) {
@ -223,7 +275,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step); pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step);
} else } else
pageIndicatorIndex.setValue(pageCount); pageIndicatorIndex.setValue(pageCount);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) performPageChange(); if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
} }
public void decrementPageRequestValue(int step, boolean changePage) { public void decrementPageRequestValue(int step, boolean changePage) {
@ -234,7 +286,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step); pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step);
} else } else
pageIndicatorIndex.setValue(1); pageIndicatorIndex.setValue(1);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) performPageChange(); if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) loadPageIndicated();
} }
public void setPageIndicatorIndex(int pageIndicatorIndex, boolean changePage) { public void setPageIndicatorIndex(int pageIndicatorIndex, boolean changePage) {
@ -242,11 +294,28 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = this.pageIndicatorIndex.getValue(); int oldIndicatorIndex = this.pageIndicatorIndex.getValue();
this.pageIndicatorIndex.setValue(pageIndicatorIndex); this.pageIndicatorIndex.setValue(pageIndicatorIndex);
if (changePage && oldIndicatorIndex != this.pageIndicatorIndex.getValue()) performPageChange(); if (changePage && oldIndicatorIndex != this.pageIndicatorIndex.getValue()) loadPageIndicated();
} }
// <-------------Just getters, setters and helper methods below here----------------> // <-------------Just getters, setters and helper methods below here---------------->
public void setRemoveVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener removeVoteTaskStartedListener) {
this.removeVoteTaskStartedListener = removeVoteTaskStartedListener;
}
public void setRemoveVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> removeVoteTaskFinishedListener) {
this.removeVoteTaskFinishedListener = removeVoteTaskFinishedListener;
}
public void setVoteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener voteTaskStartedListener) {
this.voteTaskStartedListener = voteTaskStartedListener;
}
public void setVoteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> voteTaskFinishedListener) {
this.voteTaskFinishedListener = voteTaskFinishedListener;
}
public MutableLiveData<String> getTopicViewers() { public MutableLiveData<String> getTopicViewers() {
return topicViewers; return topicViewers;
} }
@ -263,8 +332,8 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
return focusedPostIndex; return focusedPostIndex;
} }
public MutableLiveData<ArrayList<Post>> getPostsList() { public MutableLiveData<ArrayList<TopicItem>> getTopicItems() {
return postsList; return topicItems;
} }
public MutableLiveData<String> getReplyPageUrl() { public MutableLiveData<String> getReplyPageUrl() {
@ -315,11 +384,11 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public void setDeleteTaskStartedListener(DeleteTask.OnParseTaskStartedListener deleteTaskStartedListener) { public void setDeleteTaskStartedListener(ExternalAsyncTask.OnTaskStartedListener deleteTaskStartedListener) {
this.deleteTaskStartedListener = deleteTaskStartedListener; this.deleteTaskStartedListener = deleteTaskStartedListener;
} }
public void setDeleteTaskFinishedListener(DeleteTask.OnParseTaskFinishedListener<Void> deleteTaskFinishedListener) { public void setDeleteTaskFinishedListener(NetworkTask.OnNetworkTaskFinishedListener<Void> deleteTaskFinishedListener) {
this.deleteTaskFinishedListener = deleteTaskFinishedListener; this.deleteTaskFinishedListener = deleteTaskFinishedListener;
} }
@ -394,8 +463,8 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public int postCount() { public int postCount() {
if (postsList.getValue() == null) if (topicItems.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
return postsList.getValue().size(); return topicItems.getValue().size();
} }
} }

78
app/src/main/res/layout/activity_topic_poll.xml

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/question_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primary_text" />
<LinearLayout
android:id="@+id/options_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" />
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/vote_chart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<TextView
android:id="@+id/error_too_many_checked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/error_too_many_checked"
android:textColor="@color/red"
android:visibility="gone" />
<!-- controls -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/remove_vote_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/remove_vote_button"
android:visibility="gone"
android:layout_marginEnd="16dp"/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/show_poll_results_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/show_vote_results_button"
android:visibility="gone" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/show_poll_options_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/show_vote_options_button"
android:visibility="gone" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/submit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/submit"
android:textColor="@color/accent"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

4
app/src/main/res/values/strings.xml

@ -59,6 +59,10 @@
<string name="generic_network_error">Network error</string> <string name="generic_network_error">Network error</string>
<string name="retry">retry</string> <string name="retry">retry</string>
<string name="unauthorized_topic_error">This topic is either missing or off limits to you</string> <string name="unauthorized_topic_error">This topic is either missing or off limits to you</string>
<string name="remove_vote_button">Remove vote</string>
<string name="show_vote_results_button">show results</string>
<string name="error_too_many_checked">You may only select %d options</string>
<string name="show_vote_options_button">hide results</string>
<!--Profile Activity--> <!--Profile Activity-->
<string name="username">Username</string> <string name="username">Username</string>

Loading…
Cancel
Save