Browse Source

Merge branch 'develop' into editor_view

pull/45/head
Thodoris1999 6 years ago
parent
commit
5b99bfbddb
  1. 9
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  2. 351
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  3. 42
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  4. 22
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  5. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java
  6. 13
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java
  7. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java
  8. 53
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
  9. 31
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
  10. 213
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java
  11. 14
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java
  12. 179
      app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java
  13. 16
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  14. 15
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java
  15. 17
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java
  16. 20
      app/src/main/java/gr/thmmy/mthmmy/utils/parsing/ParseHelpers.java
  17. 188
      app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
  18. 13
      app/src/main/res/drawable/mention_card.xml
  19. 10
      app/src/main/res/layout/activity_topic.xml
  20. 8
      app/src/main/res/layout/activity_topic_overflow_menu.xml
  21. 1
      app/src/main/res/values/colors.xml
  22. 4
      app/src/main/res/values/strings.xml

9
app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java

@ -5,6 +5,7 @@ import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
@ -13,6 +14,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatDelegate;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
@ -92,6 +94,13 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
private String username; private String username;
private int tabSelect; private int tabSelect;
//Fix for vector drawables on android <21
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

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

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate; import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -30,7 +31,6 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask; import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
@ -45,6 +45,7 @@ import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
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.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;
import timber.log.Timber; import timber.log.Timber;
@ -58,9 +59,7 @@ import static gr.thmmy.mthmmy.services.NotificationService.NEW_POST_TAG;
* key {@link #BUNDLE_TOPIC_TITLE} for faster title rendering. * key {@link #BUNDLE_TOPIC_TITLE} for faster title rendering.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskObserver, public class TopicActivity extends BaseActivity implements TopicAdapter.OnPostFocusChangeListener {
DeleteTask.DeleteTaskCallbacks, ReplyTask.ReplyTaskCallbacks, PrepareForEditTask.PrepareForEditCallbacks,
EditTask.EditTaskCallbacks, PrepareForReply.PrepareForReplyCallbacks, TopicAdapter.OnPostFocusChangeListener {
//Activity's variables //Activity's variables
/** /**
* The key to use when putting topic's url String to {@link TopicActivity}'s Bundle. * The key to use when putting topic's url String to {@link TopicActivity}'s Bundle.
@ -103,11 +102,6 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
* long click is held in either first or last buttons * long click is held in either first or last buttons
*/ */
private static final int LARGE_STEP = 10; private static final int LARGE_STEP = 10;
/**
* Holds the value (index) of the page to be requested when a user interaction with bottom
* navigation bar occurs
*/
private Integer pageRequestValue;
//Bottom navigation bar graphics related //Bottom navigation bar graphics related
private LinearLayout bottomNavBar; private LinearLayout bottomNavBar;
@ -116,6 +110,7 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
private TextView pageIndicator; private TextView pageIndicator;
private ImageButton nextPage; private ImageButton nextPage;
private ImageButton lastPage; private ImageButton lastPage;
private Snackbar snackbar;
private TopicViewModel viewModel; private TopicViewModel viewModel;
//Fix for vector drawables on android <21 //Fix for vector drawables on android <21
@ -129,14 +124,9 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_topic); setContentView(R.layout.activity_topic);
// get TopicViewModel instance
viewModel = ViewModelProviders.of(this).get(TopicViewModel.class); viewModel = ViewModelProviders.of(this).get(TopicViewModel.class);
viewModel.setTopicTaskObserver(this); subscribeUI();
viewModel.setDeleteTaskCallbacks(this);
viewModel.setReplyFinishListener(this);
viewModel.setPrepareForEditCallbacks(this);
viewModel.setEditTaskCallbacks(this);
viewModel.setPrepareForReplyCallbacks(this);
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
String topicTitle = extras.getString(BUNDLE_TOPIC_TITLE); String topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
@ -206,81 +196,7 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
paginationEnabled(false); paginationEnabled(false);
viewModel.getTopicTaskResult().observe(this, topicTaskResult -> { viewModel.loadUrl(topicPageUrl);
if (topicTaskResult == null) {
progressBar.setVisibility(ProgressBar.VISIBLE);
} else {
switch (topicTaskResult.getResultCode()) {
case SUCCESS:
if (topicTitle == null || Objects.equals(topicTitle, "")
|| !Objects.equals(topicTitle, topicTaskResult.getTopicTitle())) {
toolbarTitle.setText(topicTaskResult.getTopicTitle());
}
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
postsList.clear();
postsList.addAll(topicTaskResult.getNewPostsList());
topicAdapter.notifyDataSetChanged();
pageIndicator.setText(String.valueOf(topicTaskResult.getCurrentPageIndex()) + "/" +
String.valueOf(topicTaskResult.getPageCount()));
pageRequestValue = topicTaskResult.getCurrentPageIndex();
paginationEnabled(true);
if (topicTaskResult.getCurrentPageIndex() == topicTaskResult.getPageCount()) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
notificationManager.cancel(NEW_POST_TAG, topicTaskResult.getLoadedPageTopicId());
}
progressBar.setVisibility(ProgressBar.GONE);
if (topicTaskResult.getReplyPageUrl() == null)
replyFAB.hide();
else
replyFAB.show();
recyclerView.scrollToPosition(topicTaskResult.getFocusedPostIndex());
break;
case NETWORK_ERROR:
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
break;
case UNAUTHORIZED:
progressBar.setVisibility(ProgressBar.GONE);
Toast.makeText(getBaseContext(), "This topic is either missing or off limits to you", Toast.LENGTH_SHORT).show();
break;
default:
//Parse failed - should never happen
Timber.d("Parse failed!"); //TODO report ParseException!!!
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish();
break;
}
}
});
viewModel.getPrepareForReplyResult().observe(this, prepareForReplyResult -> {
if (prepareForReplyResult != null) {
//prepare for a reply
postsList.add(Post.newQuickReply());
topicAdapter.notifyItemInserted(postsList.size());
recyclerView.scrollToPosition(postsList.size() - 1);
progressBar.setVisibility(ProgressBar.GONE);
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
}
});
viewModel.getPrepareForEditResult().observe(this, result -> {
if (result != null && result.isSuccessful()) {
viewModel.setEditingPost(true);
postsList.get(result.getPosition()).setPostType(Post.TYPE_EDIT);
topicAdapter.notifyItemChanged(result.getPosition());
recyclerView.scrollToPosition(result.getPosition());
progressBar.setVisibility(ProgressBar.GONE);
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
}
});
viewModel.initialLoad(topicPageUrl);
} }
@Override @Override
@ -310,18 +226,14 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
TextView usersViewing = infoDialog.findViewById(R.id.users_viewing); TextView usersViewing = infoDialog.findViewById(R.id.users_viewing);
usersViewing.setText(new SpannableStringBuilder("Loading...")); usersViewing.setText(new SpannableStringBuilder("Loading..."));
usersViewing.setMovementMethod(LinkMovementMethod.getInstance()); usersViewing.setMovementMethod(LinkMovementMethod.getInstance());
viewModel.getTopicTaskResult().observe(this, topicTaskResult -> { viewModel.getTopicTreeAndMods().observe(this, topicTreeAndMods -> {
if (topicTaskResult == null) { if (topicTreeAndMods == null) return;
usersViewing.setText(new SpannableStringBuilder("Loading...")); treeAndMods.setText(HTMLUtils.getSpannableFromHtml(this, topicTreeAndMods));
treeAndMods.setText(new SpannableStringBuilder("Loading...")); });
} else { viewModel.getTopicViewers().observe(this, topicViewers -> {
String treeAndModsString = topicTaskResult.getTopicTreeAndMods(); if (topicViewers == null) return;
treeAndMods.setText(HTMLUtils.getSpannableFromHtml(this, treeAndModsString)); usersViewing.setText(HTMLUtils.getSpannableFromHtml(this, topicViewers));
String topicViewersString = topicTaskResult.getTopicViewers();
usersViewing.setText(HTMLUtils.getSpannableFromHtml(this, topicViewersString));
}
}); });
builder.setView(infoDialog); builder.setView(infoDialog);
AlertDialog dialog = builder.create(); AlertDialog dialog = builder.create();
dialog.show(); dialog.show();
@ -400,10 +312,10 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
public void run() { public void run() {
long REPEAT_DELAY = 250; long REPEAT_DELAY = 250;
if (autoIncrement) { if (autoIncrement) {
incrementPageRequestValue(step); viewModel.incrementPageRequestValue(step, false);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY); repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
} else if (autoDecrement) { } else if (autoDecrement) {
decrementPageRequestValue(step); viewModel.decrementPageRequestValue(step, false);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY); repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
} }
} }
@ -443,11 +355,9 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
// Increment once for a click // Increment once for a click
increment.setOnClickListener(v -> { increment.setOnClickListener(v -> {
if (!autoIncrement && step == LARGE_STEP) { if (!autoIncrement && step == LARGE_STEP) {
incrementPageRequestValue(viewModel.getPageCount()); viewModel.setPageIndicatorIndex(viewModel.getPageCount(), true);
viewModel.changePage(viewModel.getPageCount() - 1);
} else if (!autoIncrement) { } else if (!autoIncrement) {
incrementPageRequestValue(step); viewModel.incrementPageRequestValue(step, true);
viewModel.changePage(pageRequestValue - 1);
} }
}); });
@ -471,11 +381,11 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
} 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.changePage(pageRequestValue - 1); viewModel.performPageChange();
} 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;
decrementPageRequestValue(pageRequestValue - viewModel.getCurrentPageIndex()); viewModel.setPageIndicatorIndex(viewModel.getCurrentPageIndex(), false);
paginationEnabled(true); paginationEnabled(true);
} }
} }
@ -489,11 +399,9 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
// Decrement once for a click // Decrement once for a click
decrement.setOnClickListener(v -> { decrement.setOnClickListener(v -> {
if (!autoDecrement && step == LARGE_STEP) { if (!autoDecrement && step == LARGE_STEP) {
decrementPageRequestValue(viewModel.getPageCount()); viewModel.setPageIndicatorIndex(1, true);
viewModel.changePage(0);
} else if (!autoDecrement) { } else if (!autoDecrement) {
decrementPageRequestValue(step); viewModel.decrementPageRequestValue(step, true);
viewModel.changePage(pageRequestValue - 1);
} }
}); });
@ -517,12 +425,12 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
} 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.changePage(pageRequestValue - 1); viewModel.performPageChange();
} 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())) {
autoIncrement = false; autoIncrement = false;
incrementPageRequestValue(viewModel.getCurrentPageIndex() - pageRequestValue); viewModel.setPageIndicatorIndex(viewModel.getCurrentPageIndex(), false);
paginationEnabled(true); paginationEnabled(true);
} }
} }
@ -531,34 +439,42 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
}); });
} }
private void incrementPageRequestValue(int step) {
if (pageRequestValue < viewModel.getPageCount() - step) {
pageRequestValue = pageRequestValue + step;
} else
pageRequestValue = viewModel.getPageCount();
pageIndicator.setText(pageRequestValue + "/" + String.valueOf(viewModel.getPageCount()));
}
private void decrementPageRequestValue(int step) {
if (pageRequestValue > step)
pageRequestValue = pageRequestValue - step;
else
pageRequestValue = 1;
pageIndicator.setText(pageRequestValue + "/" + String.valueOf(viewModel.getPageCount()));
}
//------------------------------------BOTTOM NAV BAR METHODS END------------------------------------ //------------------------------------BOTTOM NAV BAR METHODS END------------------------------------
/**
* Binds the UI to its data
*/
private void subscribeUI() {
// Implement async task callbacks
viewModel.setTopicTaskObserver(new TopicTask.TopicTaskObserver() {
@Override @Override
public void onTopicTaskStarted() { public void onTopicTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
if (snackbar != null) snackbar.dismiss();
} }
@Override @Override
public void onTopicTaskCancelled() { public void onTopicTaskCancelled() {
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
} }
});
viewModel.setDeleteTaskCallbacks(new DeleteTask.DeleteTaskCallbacks() {
@Override
public void onDeleteTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
public void onDeleteTaskFinished(boolean result) {
progressBar.setVisibility(ProgressBar.GONE);
if (result)
viewModel.reloadPage();
else
Toast.makeText(getBaseContext(), "Delete failed!", Toast.LENGTH_SHORT).show();
}
});
viewModel.setReplyFinishListener(new ReplyTask.ReplyTaskCallbacks() {
@Override @Override
public void onReplyTaskStarted() { public void onReplyTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
@ -572,85 +488,180 @@ public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskOb
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
} }
postsList.remove(postsList.size() - 1);
topicAdapter.notifyItemRemoved(postsList.size());
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
if (success) {
replyFAB.show(); replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE); bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setWritingReply(false); viewModel.setWritingReply(false);
if (success) {
if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0) { if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0) {
viewModel.loadUrl(viewModel.getBaseUrl() + "." + 2147483647); viewModel.loadUrl(ParseHelpers.getBaseURL(viewModel.getTopicUrl()) + "." + 2147483647);
} else { } else {
viewModel.reloadPage(); viewModel.reloadPage();
} }
} else { } else {
Toast.makeText(TopicActivity.this, "Post failed!", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Post failed!", Toast.LENGTH_SHORT).show();
recyclerView.getChildAt(postsList.size() - 1).setAlpha(1);
recyclerView.getChildAt(postsList.size() - 1).setEnabled(true);
} }
} }
});
viewModel.setPrepareForEditCallbacks(new PrepareForEditTask.PrepareForEditCallbacks() {
@Override @Override
public void onPrepareForReplyStarted() { public void onPrepareEditStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
} }
@Override @Override
public void onPrepareForReplyCancelled() { public void onPrepareEditCancelled() {
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
} }
});
viewModel.setEditTaskCallbacks(new EditTask.EditTaskCallbacks() {
@Override @Override
public void onDeleteTaskStarted() { public void onEditTaskStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
} }
@Override @Override
public void onDeleteTaskFinished(boolean result) { public void onEditTaskFinished(boolean result, int position) {
View view = getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
if (result) { if (result) {
postsList.get(position).setPostType(Post.TYPE_POST);
topicAdapter.notifyItemChanged(position);
replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE);
viewModel.setEditingPost(false);
viewModel.reloadPage(); viewModel.reloadPage();
} else { } else {
Toast.makeText(TopicActivity.this, "Post deleted!", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Edit failed!", Toast.LENGTH_SHORT).show();
recyclerView.getChildAt(viewModel.getPostBeingEditedPosition()).setAlpha(1);
recyclerView.getChildAt(viewModel.getPostBeingEditedPosition()).setEnabled(true);
} }
} }
});
viewModel.setPrepareForReplyCallbacks(new PrepareForReply.PrepareForReplyCallbacks() {
@Override @Override
public void onPrepareEditStarted() { public void onPrepareForReplyStarted() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
} }
@Override @Override
public void onPrepareEditCancelled() { public void onPrepareForReplyCancelled() {
progressBar.setVisibility(ProgressBar.GONE); progressBar.setVisibility(ProgressBar.GONE);
} }
});
@Override // observe the chages in data
public void onEditTaskStarted() { viewModel.getPageIndicatorIndex().observe(this, pageIndicatorIndex -> {
progressBar.setVisibility(ProgressBar.VISIBLE); if (pageIndicatorIndex == null) return;
} pageIndicator.setText(String.valueOf(pageIndicatorIndex) + "/" +
String.valueOf(viewModel.getPageCount()));
@Override });
public void onEditTaskFinished(boolean result, int position) { viewModel.getTopicTitle().observe(this, newTopicTitle -> {
View view = getCurrentFocus(); if (newTopicTitle == null) return;
if (view != null) { if (!TextUtils.equals(toolbarTitle.getText(), newTopicTitle))
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); toolbarTitle.setText(newTopicTitle);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); });
viewModel.getPageTopicId().observe(this, pageTopicId -> {
if (pageTopicId == null) return;
if (viewModel.getCurrentPageIndex() == viewModel.getPageCount()) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
notificationManager.cancel(NEW_POST_TAG, pageTopicId);
} }
});
postsList.get(position).setPostType(Post.TYPE_POST); viewModel.getReplyPageUrl().observe(this, replyPageUrl -> {
topicAdapter.notifyItemChanged(position); if (replyPageUrl == null)
viewModel.setEditingPost(false); replyFAB.hide();
progressBar.setVisibility(ProgressBar.GONE); else
replyFAB.show(); replyFAB.show();
bottomNavBar.setVisibility(View.VISIBLE); });
viewModel.getPostsList().observe(this, postList -> {
if (result) { if (postList == null) progressBar.setVisibility(ProgressBar.VISIBLE);
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
postsList.clear();
postsList.addAll(postList);
topicAdapter.notifyDataSetChanged();
});
viewModel.getFocusedPostIndex().observe(this, focusedPostIndex -> {
if (focusedPostIndex == null) return;
recyclerView.scrollToPosition(focusedPostIndex);
});
viewModel.getTopicTaskResultCode().observe(this, resultCode -> {
if (resultCode == null) return;
progressBar.setVisibility(ProgressBar.GONE);
switch (resultCode) {
case SUCCESS:
paginationEnabled(true);
break;
case NETWORK_ERROR:
if (viewModel.getPostsList().getValue() == null) {
// no page has been loaded yet. Give user the ability to refresh
recyclerView.setVisibility(View.GONE);
TextView errorTextview = findViewById(R.id.error_textview);
errorTextview.setText(getString(R.string.network_error_retry_prompt));
errorTextview.setVisibility(View.VISIBLE);
errorTextview.setOnClickListener(view -> {
viewModel.reloadPage(); viewModel.reloadPage();
errorTextview.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
});
} else { } else {
Toast.makeText(TopicActivity.this, "Edit failed!", Toast.LENGTH_SHORT).show(); // a page has already been loaded
viewModel.setPageIndicatorIndex(viewModel.getCurrentPageIndex(), false);
snackbar = Snackbar.make(findViewById(R.id.main_content),
R.string.generic_network_error, Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.retry, view -> viewModel.reloadPage());
snackbar.show();
}
break;
case UNAUTHORIZED:
recyclerView.setVisibility(View.GONE);
TextView errorTextview = findViewById(R.id.error_textview);
errorTextview.setText(getString(R.string.unauthorized_topic_error));
errorTextview.setVisibility(View.VISIBLE);
break;
default:
//Parse failed - should never happen
Timber.d("Parse failed!"); //TODO report ParseException!!!
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish();
break;
} }
});
viewModel.getPrepareForReplyResult().observe(this, prepareForReplyResult -> {
progressBar.setVisibility(ProgressBar.GONE);
if (prepareForReplyResult != null && prepareForReplyResult.isSuccessful()) {
//prepare for a reply
viewModel.setWritingReply(true);
postsList.add(Post.newQuickReply());
topicAdapter.notifyItemInserted(postsList.size());
recyclerView.scrollToPosition(postsList.size() - 1);
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
} else {
Snackbar.make(findViewById(R.id.main_content), getString(R.string.generic_network_error), Snackbar.LENGTH_SHORT).show();
}
});
viewModel.getPrepareForEditResult().observe(this, result -> {
progressBar.setVisibility(ProgressBar.GONE);
if (result != null && result.isSuccessful()) {
viewModel.setEditingPost(true);
postsList.get(result.getPosition()).setPostType(Post.TYPE_EDIT);
topicAdapter.notifyItemChanged(result.getPosition());
recyclerView.scrollToPosition(result.getPosition());
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
} else {
Snackbar.make(findViewById(R.id.main_content), getString(R.string.generic_network_error), Snackbar.LENGTH_SHORT).show();
}
});
} }
} }

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

@ -46,6 +46,7 @@ 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.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
import gr.thmmy.mthmmy.viewmodel.TopicViewModel; import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import timber.log.Timber; import timber.log.Timber;
@ -284,8 +285,16 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.stars.setVisibility(View.VISIBLE); holder.stars.setVisibility(View.VISIBLE);
} else } else
holder.stars.setVisibility(View.GONE); 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! //Special card for special member of the month!
if (mUserColor == TopicParser.USER_COLOR_PINK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.cardChildLinear.setBackground(context.getResources(). holder.cardChildLinear.setBackground(context.getResources().
getDrawable(R.drawable.member_of_the_month_card, null)); getDrawable(R.drawable.member_of_the_month_card, null));
@ -444,7 +453,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
.transform(new CircleTransform()) .transform(new CircleTransform())
.into(holder.thumbnail); .into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername()); holder.username.setText(getSessionManager().getUsername());
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle()); holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle().getValue());
holder.quickReply.setText(viewModel.getBuildedQuotes()); holder.quickReply.setText(viewModel.getBuildedQuotes());
@ -452,15 +461,13 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.submitButton.setOnClickListener(view -> { holder.submitButton.setOnClickListener(view -> {
if (holder.quickReplySubject.getText().toString().isEmpty()) return; if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.quickReply.getText().toString().isEmpty()) return; if (holder.quickReply.getText().toString().isEmpty()) return;
holder.submitButton.setEnabled(false); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false);
viewModel.postReply(context, holder.quickReplySubject.getText().toString(), viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
holder.quickReply.getText().toString()); holder.quickReply.getText().toString());
holder.quickReplySubject.getText().clear();
holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle());
holder.quickReply.getText().clear();
holder.submitButton.setEnabled(true);
}); });
@ -490,13 +497,12 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.submitButton.setOnClickListener(view -> { holder.submitButton.setOnClickListener(view -> {
if (holder.editSubject.getText().toString().isEmpty()) return; if (holder.editSubject.getText().toString().isEmpty()) return;
if (holder.editMessage.getText().toString().isEmpty()) return; if (holder.editMessage.getText().toString().isEmpty()) return;
holder.submitButton.setEnabled(false); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
holder.itemView.setAlpha(0.5f);
holder.itemView.setEnabled(false);
viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editMessage.getText().toString()); viewModel.editPost(position, holder.editSubject.getText().toString(), holder.editMessage.getText().toString());
holder.editSubject.getText().clear();
holder.editSubject.setText(postsList.get(position).getSubject());
holder.submitButton.setEnabled(true);
}); });
if (backPressHidden) { if (backPressHidden) {
@ -628,8 +634,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (target.is(ThmmyPage.PageCategory.TOPIC)) { if (target.is(ThmmyPage.PageCategory.TOPIC)) {
//This url points to a topic //This url points to a topic
//Checks if the page to be loaded is the one already shown //Checks if the page to be loaded is the one already shown
if (uriString.contains(viewModel.getBaseUrl())) { if (uriString.contains(ParseHelpers.getBaseURL(viewModel.getTopicUrl()))) {
Timber.e("reached here!");
if (uriString.contains("topicseen#new") || uriString.contains("#new")) { if (uriString.contains("topicseen#new") || uriString.contains("#new")) {
if (viewModel.getCurrentPageIndex() == viewModel.getPageCount()) { if (viewModel.getCurrentPageIndex() == viewModel.getPageCount()) {
//same page //same page
@ -643,7 +648,6 @@ 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);
Timber.e("reached tthere! %s", testAgainst);
for (int i = 0; i < postsList.size(); i++) { for (int i = 0; i < postsList.size(); i++) {
if (postsList.get(i).getPostIndex() == testAgainst) { if (postsList.get(i).getPostIndex() == testAgainst) {
//same page //same page
@ -652,11 +656,11 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
return true; return true;
} }
} }
} else if ((Objects.equals(uriString, viewModel.getBaseUrl()) && viewModel.getCurrentPageIndex() == 1) || } else if ((Objects.equals(uriString, ParseHelpers.getBaseURL(viewModel.getTopicUrl())) &&
Integer.parseInt(uriString.substring(viewModel.getBaseUrl().length() + 1)) / 15 + 1 == viewModel.getCurrentPageIndex() == 1) ||
Integer.parseInt(uriString.substring(ParseHelpers.getBaseURL(viewModel.getTopicUrl()).length() + 1)) / 15 + 1 ==
viewModel.getCurrentPageIndex()) { viewModel.getCurrentPageIndex()) {
//same page //same page
Timber.e("ha");
return true; return true;
} }
} }

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

@ -1,7 +1,9 @@
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.Connection;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
@ -13,7 +15,9 @@ 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.Pattern;
import gr.thmmy.mthmmy.base.BaseActivity;
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.utils.parsing.ParseHelpers; import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
@ -29,6 +33,10 @@ import timber.log.Timber;
* <li>{@link #parseTopic(Document, ParseHelpers.Language)}</li> * <li>{@link #parseTopic(Document, ParseHelpers.Language)}</li>
*/ */
public class TopicParser { public class TopicParser {
private static Pattern mentionsPattern = Pattern.
compile("<div class=\"quoteheader\">\\n\\s+?<a href=.+?>Quote from: "
+ BaseActivity.getSessionManager().getUsername());
//User colors //User colors
private static final int USER_COLOR_BLACK = Color.parseColor("#000000"); private static final int USER_COLOR_BLACK = Color.parseColor("#000000");
private static final int USER_COLOR_RED = Color.parseColor("#F44336"); private static final int USER_COLOR_RED = Color.parseColor("#F44336");
@ -159,7 +167,7 @@ public class TopicParser {
p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate, p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate,
p_postURL, p_deletePostURL, p_editPostURL; p_postURL, p_deletePostURL, p_editPostURL;
int p_postNum, p_postIndex, p_numberOfStars, p_userColor; int p_postNum, p_postIndex, p_numberOfStars, p_userColor;
boolean p_isDeleted = false; boolean p_isDeleted = false, p_isUserMentionedInPost = false;
ArrayList<ThmmyFile> p_attachedFiles; ArrayList<ThmmyFile> p_attachedFiles;
//Initialize variables //Initialize variables
@ -434,17 +442,25 @@ public class TopicParser {
} }
} }
//Checks post for mentions of this user (if the user is logged in)
if (BaseActivity.getSessionManager().isLoggedIn() &&
mentionsPattern.matcher(p_post).find()) {
p_isUserMentionedInPost = true;
}
//Add new post in postsList, extended information needed //Add new post in postsList, extended information needed
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender , p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender
, p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor , p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
, p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL, Post.TYPE_POST)); , p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST));
} else { //Deleted user } else { //Deleted user
//Add new post in postsList, only standard information needed //Add new post in postsList, only standard information needed
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post
, p_postIndex, p_postNum, p_postDate, p_userColor, p_attachedFiles , p_postIndex, p_postNum, p_postDate, p_userColor, p_attachedFiles
, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL, Post.TYPE_POST)); , p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL
, p_isUserMentionedInPost, Post.TYPE_POST));
} }
} }
return parsedPostsList; return parsedPostsList;

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

@ -41,7 +41,7 @@ public class DeleteTask extends AsyncTask<String, Void, Boolean> {
return true; return true;
default: default:
Timber.e("Something went wrong. Request string: %s", delete.toString()); Timber.e("Something went wrong. Request string: %s", delete.toString());
return true; return false;
} }
} catch (IOException e) { } catch (IOException e) {
Timber.e(e, "Delete failed."); Timber.e(e, "Delete failed.");

13
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java

@ -33,18 +33,13 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
@Override @Override
protected PrepareForReplyResult doInBackground(Integer... postIndices) { protected PrepareForReplyResult doInBackground(Integer... postIndices) {
String numReplies = null;
String seqnum = null;
String sc = null;
String topic = null;
StringBuilder buildedQuotes = new StringBuilder("");
Document document; Document document;
Request request = new Request.Builder() Request request = new Request.Builder()
.url(replyPageUrl + ";wap2") .url(replyPageUrl + ";wap2")
.build(); .build();
OkHttpClient client = BaseApplication.getInstance().getClient(); OkHttpClient client = BaseApplication.getInstance().getClient();
String numReplies, seqnum, sc, topic;
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string()); document = Jsoup.parse(response.body().string());
@ -55,14 +50,15 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
topic = document.select("input[name=topic]").first().attr("value"); topic = document.select("input[name=topic]").first().attr("value");
} catch (IOException | Selector.SelectorParseException e) { } catch (IOException | Selector.SelectorParseException e) {
Timber.e(e, "Prepare failed."); Timber.e(e, "Prepare failed.");
return new PrepareForReplyResult(false, null, null, null, null, null);
} }
StringBuilder buildedQuotes = new StringBuilder("");
for (Integer postIndex : postIndices) { for (Integer postIndex : postIndices) {
request = new Request.Builder() request = new Request.Builder()
.url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" + .url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" +
postIndex + ";" + "sesc=" + sc + ";xml") postIndex + ";" + "sesc=" + sc + ";xml")
.build(); .build();
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
String body = response.body().string(); String body = response.body().string();
@ -70,9 +66,10 @@ public class PrepareForReply extends AsyncTask<Integer, Void, PrepareForReplyRes
buildedQuotes.append("\n\n"); buildedQuotes.append("\n\n");
} catch (IOException | Selector.SelectorParseException e) { } catch (IOException | Selector.SelectorParseException e) {
Timber.e(e, "Quote building failed."); Timber.e(e, "Quote building failed.");
return new PrepareForReplyResult(false, null, null, null, null, null);
} }
} }
return new PrepareForReplyResult(numReplies, seqnum, sc, topic, buildedQuotes.toString()); return new PrepareForReplyResult(true, numReplies, seqnum, sc, topic, buildedQuotes.toString());
} }
@Override @Override

8
app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java

@ -2,9 +2,11 @@ package gr.thmmy.mthmmy.activities.topic.tasks;
public class PrepareForReplyResult { public class PrepareForReplyResult {
private final String numReplies, seqnum, sc, topic, buildedQuotes; private final String numReplies, seqnum, sc, topic, buildedQuotes;
private boolean successful;
public PrepareForReplyResult(String numReplies, String seqnum, String sc, String topic, String buildedQuotes) { public PrepareForReplyResult(boolean successful, String numReplies, String seqnum, String sc, String topic, String buildedQuotes) {
this.successful = successful;
this.numReplies = numReplies; this.numReplies = numReplies;
this.seqnum = seqnum; this.seqnum = seqnum;
this.sc = sc; this.sc = sc;
@ -31,4 +33,8 @@ public class PrepareForReplyResult {
public String getBuildedQuotes() { public String getBuildedQuotes() {
return buildedQuotes; return buildedQuotes;
} }
public boolean isSuccessful() {
return successful;
}
} }

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

@ -43,18 +43,6 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
@Override @Override
protected TopicTaskResult doInBackground(String... strings) { protected TopicTaskResult doInBackground(String... strings) {
String topicTitle = null;
String topicTreeAndMods = "";
String topicViewers = "";
ArrayList<Post> newPostsList = null;
int loadedPageTopicId = -1;
int focusedPostIndex = 0;
SparseArray<String> pagesUrls = new SparseArray<>();
int currentPageIndex = 1;
int pageCount = 1;
String baseUrl = "";
String lastPageLoadAttemptedUrl = "";
Document topic = null; Document topic = null;
String newPageUrl = strings[0]; String newPageUrl = strings[0];
@ -70,14 +58,9 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
} }
} }
lastPageLoadAttemptedUrl = newPageUrl;
if (strings[0].substring(0, strings[0].lastIndexOf(".")).contains("topic="))
baseUrl = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
String replyPageUrl = null;
Request request = new Request.Builder() Request request = new Request.Builder()
.url(newPageUrl) .url(newPageUrl)
.build(); .build();
ResultCode resultCode;
try { try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute(); Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
topic = Jsoup.parse(response.body().string()); topic = Jsoup.parse(response.body().string());
@ -85,17 +68,18 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
//Finds topic's tree, mods and users viewing //Finds topic's tree, mods and users viewing
topicTreeAndMods = topic.select("div.nav").first().html(); String topicTreeAndMods = topic.select("div.nav").first().html();
topicViewers = TopicParser.parseUsersViewingThisTopic(topic, language); String topicViewers = TopicParser.parseUsersViewingThisTopic(topic, language);
//Finds reply page url //Finds reply page url
String replyPageUrl = null;
Element replyButton = topic.select("a:has(img[alt=Reply])").first(); Element replyButton = topic.select("a:has(img[alt=Reply])").first();
if (replyButton == null) if (replyButton == null)
replyButton = topic.select("a:has(img[alt=Απάντηση])").first(); replyButton = topic.select("a:has(img[alt=Απάντηση])").first();
if (replyButton != null) replyPageUrl = replyButton.attr("href"); if (replyButton != null) replyPageUrl = replyButton.attr("href");
//Finds topic title if missing //Finds topic title if missing
topicTitle = topic.select("td[id=top_subject]").first().text(); String topicTitle = topic.select("td[id=top_subject]").first().text();
if (topicTitle.contains("Topic:")) { if (topicTitle.contains("Topic:")) {
topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7 topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7
, topicTitle.indexOf("(Read") - 2); , topicTitle.indexOf("(Read") - 2);
@ -106,42 +90,39 @@ public class TopicTask extends AsyncTask<String, Void, TopicTaskResult> {
} }
//Finds current page's index //Finds current page's index
currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language); int currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language);
//Finds number of pages //Finds number of pages
pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language); int pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language);
for (int i = 0; i < pageCount; i++) { ArrayList<Post> newPostsList = TopicParser.parseTopic(topic, language);
//Generate each page's url from topic's base url +".15*numberOfPage"
pagesUrls.put(i, baseUrl + "." + String.valueOf(i * 15));
}
newPostsList = TopicParser.parseTopic(topic, language);
loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(lastPageLoadAttemptedUrl)); 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;
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).getPostIndex() == postFocus) {
focusedPostIndex = i; focusedPostIndex = i;
break; break;
} }
} }
resultCode = ResultCode.SUCCESS; return new TopicTaskResult(ResultCode.SUCCESS, topicTitle, replyPageUrl, newPostsList, loadedPageTopicId,
currentPageIndex, pageCount, focusedPostIndex, topicTreeAndMods, topicViewers);
} catch (IOException e) { } catch (IOException e) {
Timber.i(e, "IO Exception"); Timber.i(e, "IO Exception");
resultCode = ResultCode.NETWORK_ERROR; return new TopicTaskResult(ResultCode.NETWORK_ERROR, null, null, null,
0, 0, 0, 0, null, null);
} catch (Exception e) { } catch (Exception e) {
if (isUnauthorized(topic)) { if (isUnauthorized(topic)) {
resultCode = ResultCode.UNAUTHORIZED; return new TopicTaskResult(ResultCode.UNAUTHORIZED, null, null, null,
0, 0, 0, 0, null, null);
} else { } else {
Timber.e(e, "Parsing Error"); Timber.e(e, "Parsing Error");
resultCode = ResultCode.PARSING_ERROR; return new TopicTaskResult(ResultCode.PARSING_ERROR, null, null, null,
0, 0, 0, 0, null, null);
} }
} }
return new TopicTaskResult(resultCode, baseUrl, topicTitle, replyPageUrl, newPostsList,
loadedPageTopicId, currentPageIndex, pageCount, focusedPostIndex, topicTreeAndMods,
topicViewers, lastPageLoadAttemptedUrl, pagesUrls);
} }
private boolean isUnauthorized(Document document) { private boolean isUnauthorized(Document document) {

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

@ -9,13 +9,6 @@ import gr.thmmy.mthmmy.model.Post;
public class TopicTaskResult { public class TopicTaskResult {
private final TopicTask.ResultCode resultCode; private final TopicTask.ResultCode resultCode;
/**
* Holds this topic's base url. For example a topic with url similar to
* "https://www.thmmy.gr/smf/index.php?topic=1.15;topicseen" or
* "https://www.thmmy.gr/smf/index.php?topic=1.msg1#msg1"
* has the base url "https://www.thmmy.gr/smf/index.php?topic=1"
*/
private final String baseUrl;
/** /**
* Holds this topic's title. At first this gets the value of the topic title that came with * Holds this topic's title. At first this gets the value of the topic title that came with
* bundle and is rendered in the toolbar while parsing this topic. Later, if a different topic * bundle and is rendered in the toolbar while parsing this topic. Later, if a different topic
@ -46,18 +39,12 @@ public class TopicTaskResult {
//Topic's info related //Topic's info related
private final String topicTreeAndMods; private final String topicTreeAndMods;
private final String topicViewers; private final String topicViewers;
/**
* The url of the last page that was attempted to be loaded
*/
private final String lastPageLoadAttemptedUrl;
private final SparseArray<String> pagesUrls;
public TopicTaskResult(TopicTask.ResultCode resultCode, String baseUrl, String topicTitle, public TopicTaskResult(TopicTask.ResultCode resultCode, String topicTitle,
String replyPageUrl, ArrayList<Post> newPostsList, int loadedPageTopicId, String replyPageUrl, ArrayList<Post> newPostsList, int loadedPageTopicId,
int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods, int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
String topicViewers, String lastPageLoadAttemptedUrl, SparseArray<String> pagesUrls) { String topicViewers) {
this.resultCode = resultCode; this.resultCode = resultCode;
this.baseUrl = baseUrl;
this.topicTitle = topicTitle; this.topicTitle = topicTitle;
this.replyPageUrl = replyPageUrl; this.replyPageUrl = replyPageUrl;
this.newPostsList = newPostsList; this.newPostsList = newPostsList;
@ -67,18 +54,12 @@ public class TopicTaskResult {
this.focusedPostIndex = focusedPostIndex; this.focusedPostIndex = focusedPostIndex;
this.topicTreeAndMods = topicTreeAndMods; this.topicTreeAndMods = topicTreeAndMods;
this.topicViewers = topicViewers; this.topicViewers = topicViewers;
this.lastPageLoadAttemptedUrl = lastPageLoadAttemptedUrl;
this.pagesUrls = pagesUrls;
} }
public TopicTask.ResultCode getResultCode() { public TopicTask.ResultCode getResultCode() {
return resultCode; return resultCode;
} }
public String getBaseUrl() {
return baseUrl;
}
public String getTopicTitle() { public String getTopicTitle() {
return topicTitle; return topicTitle;
} }
@ -114,12 +95,4 @@ public class TopicTaskResult {
public String getTopicViewers() { public String getTopicViewers() {
return topicViewers; return topicViewers;
} }
public String getLastPageLoadAttemptedUrl() {
return lastPageLoadAttemptedUrl;
}
public SparseArray<String> getPagesUrls() {
return pagesUrls;
}
} }

213
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadActivity.java

@ -2,10 +2,11 @@ package gr.thmmy.mthmmy.activities.upload;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -42,8 +43,12 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
@ -134,9 +139,7 @@ public class UploadActivity extends BaseActivity {
rootCategorySpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(uploadRootCategories)); rootCategorySpinner.setOnItemSelectedListener(new CustomOnItemSelectedListener(uploadRootCategories));
titleDescriptionBuilderButton = findViewById(R.id.upload_title_description_builder); titleDescriptionBuilderButton = findViewById(R.id.upload_title_description_builder);
titleDescriptionBuilderButton.setOnClickListener(new View.OnClickListener() { titleDescriptionBuilderButton.setOnClickListener(view -> {
@Override
public void onClick(View view) {
if (categorySelected.equals("-1")) { if (categorySelected.equals("-1")) {
Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show(); Toast.makeText(view.getContext(), "Please choose category first", Toast.LENGTH_SHORT).show();
return; return;
@ -149,10 +152,29 @@ public class UploadActivity extends BaseActivity {
return; return;
} }
String maybeSemester = (String) ((AppCompatSpinnerWithoutDefault) String maybeSemester = "", maybeCourse = "";
if (numberOfSpinners == 5) {
if (((AppCompatSpinnerWithoutDefault) categoriesSpinners.getChildAt(numberOfSpinners - 1)).
getSelectedItemPosition() == -1) {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 4)).getSelectedItem();
maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem();
} else {
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show();
}
} else if (numberOfSpinners == 4) {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 3)).getSelectedItem();
maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem();
} else {
maybeSemester = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem(); categoriesSpinners.getChildAt(numberOfSpinners - 2)).getSelectedItem();
String maybeCourse = (String) ((AppCompatSpinnerWithoutDefault) maybeCourse = (String) ((AppCompatSpinnerWithoutDefault)
categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem(); categoriesSpinners.getChildAt(numberOfSpinners - 1)).getSelectedItem();
}
if (!maybeSemester.contains("εξάμηνο") && !maybeSemester.contains("Εξάμηνο")) { if (!maybeSemester.contains("εξάμηνο") && !maybeSemester.contains("Εξάμηνο")) {
Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show(); Toast.makeText(view.getContext(), "Please choose a course category", Toast.LENGTH_SHORT).show();
@ -168,12 +190,11 @@ public class UploadActivity extends BaseActivity {
maybeSemester = maybeSemester.replaceAll("-", "").trim().substring(0, 1); maybeSemester = maybeSemester.replaceAll("-", "").trim().substring(0, 1);
Intent intent = new Intent(UploadActivity.this, UploadFieldsBuilderActivity.class); Intent intent = new Intent(UploadActivity.this, UploadFieldsBuilderActivity.class);
Bundle extras = new Bundle(); Bundle builderExtras = new Bundle();
extras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE, maybeCourse); builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_COURSE, maybeCourse);
extras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER, maybeSemester); builderExtras.putString(BUNDLE_UPLOAD_FIELD_BUILDER_SEMESTER, maybeSemester);
intent.putExtras(extras); intent.putExtras(builderExtras);
startActivityForResult(intent, REQUEST_CODE_FIELDS_BUILDER); startActivityForResult(intent, REQUEST_CODE_FIELDS_BUILDER);
}
}); });
titleDescriptionBuilderButton.setEnabled(false); titleDescriptionBuilderButton.setEnabled(false);
@ -183,19 +204,28 @@ public class UploadActivity extends BaseActivity {
selectFileButton = findViewById(R.id.upload_select_file_button); selectFileButton = findViewById(R.id.upload_select_file_button);
Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp); Drawable selectStartDrawable = AppCompatResources.getDrawable(this, R.drawable.ic_insert_drive_file_white_24dp);
selectFileButton.setCompoundDrawablesRelativeWithIntrinsicBounds(selectStartDrawable, null, null, null); selectFileButton.setCompoundDrawablesRelativeWithIntrinsicBounds(selectStartDrawable, null, null, null);
selectFileButton.setOnClickListener(new View.OnClickListener() { selectFileButton.setOnClickListener(v -> {
@Override
public void onClick(View v) {
final CharSequence[] options = {"Take photo", "Choose file", final CharSequence[] options = {"Take photo", "Choose file",
"Cancel"}; "Cancel"};
AlertDialog.Builder builder = new AlertDialog.Builder(UploadActivity.this); AlertDialog.Builder builder = new AlertDialog.Builder(UploadActivity.this);
builder.setTitle("Upload file"); builder.setTitle("Upload file");
builder.setItems(options, new DialogInterface.OnClickListener() { builder.setItems(options, (dialog, item) -> {
@Override
public void onClick(DialogInterface dialog, int item) {
if (options[item].equals("Take photo")) { if (options[item].equals("Take photo")) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); /*Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CODE_CAMERA); startActivityForResult(intent, REQUEST_CODE_CAMERA);*/
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePhotoIntent.putExtra("return-data", true);
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(UploadsHelper.getTempFile(this)));
Intent targetedIntent = new Intent(takePhotoIntent);
List<ResolveInfo> resInfo = this.getPackageManager().queryIntentActivities(takePhotoIntent, 0);
for (ResolveInfo resolveInfo : resInfo) {
String packageName = resolveInfo.activityInfo.packageName;
targetedIntent.setPackage(packageName);
}
startActivityForResult(takePhotoIntent, REQUEST_CODE_CAMERA);
} else if (options[item].equals("Choose file")) { } else if (options[item].equals("Choose file")) {
String[] mimeTypes = {"image/jpeg", "text/html", "image/png", "image/jpg", "image/gif", String[] mimeTypes = {"image/jpeg", "text/html", "image/png", "image/jpg", "image/gif",
"application/pdf", "application/rar", "application/x-tar", "application/zip", "application/pdf", "application/rar", "application/x-tar", "application/zip",
@ -210,15 +240,11 @@ public class UploadActivity extends BaseActivity {
} else if (options[item].equals("Cancel")) { } else if (options[item].equals("Cancel")) {
dialog.dismiss(); dialog.dismiss();
} }
}
}); });
builder.show(); builder.show();
}
}); });
findViewById(R.id.upload_upload_button).setOnClickListener(new View.OnClickListener() { findViewById(R.id.upload_upload_button).setOnClickListener(view -> {
@Override
public void onClick(View view) {
String uploadTitleText = uploadTitle.getText().toString(); String uploadTitleText = uploadTitle.getText().toString();
String uploadDescriptionText = uploadDescription.getText().toString(); String uploadDescriptionText = uploadDescription.getText().toString();
@ -245,7 +271,7 @@ public class UploadActivity extends BaseActivity {
String tempFilePath = null; String tempFilePath = null;
if (uploadFilename != null) { if (uploadFilename != null) {
tempFilePath = createTempFile(uploadFilename); tempFilePath = UploadsHelper.createTempFile(this, fileUri, uploadFilename);
if (tempFilePath == null) { if (tempFilePath == null) {
//Something went wrong, abort //Something went wrong, abort
return; return;
@ -277,8 +303,8 @@ public class UploadActivity extends BaseActivity {
Exception exception) { Exception exception) {
Toast.makeText(context, "Upload failed", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Upload failed", Toast.LENGTH_SHORT).show();
if (finalTempFilePath != null) { if (finalTempFilePath != null) {
if (!deleteTempFile(finalTempFilePath)) { if (!UploadsHelper.deleteTempFile(finalTempFilePath)) {
Toast.makeText(context, "Failed to delete temp file", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to delete temporary file", Toast.LENGTH_SHORT).show();
} }
} }
} }
@ -286,8 +312,8 @@ public class UploadActivity extends BaseActivity {
@Override @Override
public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
if (finalTempFilePath != null) { if (finalTempFilePath != null) {
if (!deleteTempFile(finalTempFilePath)) { if (!UploadsHelper.deleteTempFile(finalTempFilePath)) {
Toast.makeText(context, "Failed to delete temp file", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to delete temporary file", Toast.LENGTH_SHORT).show();
} }
} }
} }
@ -295,8 +321,8 @@ public class UploadActivity extends BaseActivity {
@Override @Override
public void onCancelled(Context context, UploadInfo uploadInfo) { public void onCancelled(Context context, UploadInfo uploadInfo) {
if (finalTempFilePath != null) { if (finalTempFilePath != null) {
if (!deleteTempFile(finalTempFilePath)) { if (!UploadsHelper.deleteTempFile(finalTempFilePath)) {
Toast.makeText(context, "Failed to delete temp file", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to delete temporary file", Toast.LENGTH_SHORT).show();
} }
} }
} }
@ -305,7 +331,6 @@ public class UploadActivity extends BaseActivity {
} catch (Exception exception) { } catch (Exception exception) {
Timber.e(exception, "AndroidUploadService: %s", exception.getMessage()); Timber.e(exception, "AndroidUploadService: %s", exception.getMessage());
} }
}
}); });
if (uploadRootCategories.isEmpty()) { if (uploadRootCategories.isEmpty()) {
@ -326,7 +351,7 @@ public class UploadActivity extends BaseActivity {
if (bundleCategory != null) { if (bundleCategory != null) {
int bundleSelectionIndex = -1, currentIndex = 0; int bundleSelectionIndex = -1, currentIndex = 0;
for (UploadCategory category : uploadRootCategories) { for (UploadCategory category : uploadRootCategories) {
if (category.getCategoryTitle().contains(bundleCategory.get(0))) { //TODO fix .contains, always false if (bundleCategory.get(0).contains(category.getCategoryTitle())) {
bundleSelectionIndex = currentIndex; bundleSelectionIndex = currentIndex;
break; break;
} }
@ -335,6 +360,7 @@ public class UploadActivity extends BaseActivity {
if (bundleSelectionIndex != -1) { if (bundleSelectionIndex != -1) {
rootCategorySpinner.setSelection(bundleSelectionIndex, true); rootCategorySpinner.setSelection(bundleSelectionIndex, true);
bundleCategory.remove(0);
} }
} }
@ -373,7 +399,7 @@ public class UploadActivity extends BaseActivity {
fileUri = data.getData(); fileUri = data.getData();
if (fileUri != null) { if (fileUri != null) {
String filename = filenameFromUri(fileUri); String filename = UploadsHelper.filenameFromUri(this, fileUri);
selectFileButton.setText(filename); selectFileButton.setText(filename);
filename = filename.toLowerCase(); filename = filename.toLowerCase();
@ -396,11 +422,37 @@ public class UploadActivity extends BaseActivity {
fileIcon = "blank.gif"; fileIcon = "blank.gif";
} }
} }
} else if (requestCode == REQUEST_CODE_CAMERA && data != null) { } else if (requestCode == REQUEST_CODE_CAMERA) {
if (resultCode == Activity.RESULT_CANCELED) { if (resultCode == Activity.RESULT_CANCELED) {
return; return;
} }
//TODO
Bitmap bitmap;
File cacheImageFile = UploadsHelper.getTempFile(this);
if (resultCode == Activity.RESULT_OK) {
fileUri = Uri.fromFile(cacheImageFile);
fileIcon = "jpg_image.gif";
bitmap = UploadsHelper.getImageResized(this, fileUri);
int rotation = UploadsHelper.getRotation(this, fileUri);
bitmap = UploadsHelper.rotate(bitmap, rotation);
try {
FileOutputStream out = new FileOutputStream(cacheImageFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
String newFilename = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).
format(new Date());
fileUri = Uri.parse(UploadsHelper.createTempFile(this, fileUri, newFilename));
newFilename += ".jpg";
selectFileButton.setText(newFilename);
}
} else if (requestCode == REQUEST_CODE_FIELDS_BUILDER) { } else if (requestCode == REQUEST_CODE_FIELDS_BUILDER) {
if (resultCode == Activity.RESULT_CANCELED) { if (resultCode == Activity.RESULT_CANCELED) {
return; return;
@ -414,82 +466,6 @@ public class UploadActivity extends BaseActivity {
} }
} }
@NonNull
private String filenameFromUri(Uri uri) {
String filename = null;
if (uri.getScheme().equals("content")) {
try (Cursor cursor = getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
}
}
if (filename == null) {
filename = uri.getPath();
int cut = filename.lastIndexOf('/');
if (cut != -1) {
filename = filename.substring(cut + 1);
}
}
return filename;
}
@Nullable
private String createTempFile(String newFilename) {
String oldFilename = filenameFromUri(fileUri);
String fileExtension = oldFilename.substring(oldFilename.indexOf("."));
String destinationFilename = android.os.Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension;
File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads");
if (!tempDirectory.exists()) {
if (!tempDirectory.mkdirs()) {
Timber.w("Temporary directory build returned false in %s", UploadActivity.class.getSimpleName());
Toast.makeText(this, "Couldn't create temporary directory", Toast.LENGTH_SHORT).show();
return null;
}
}
InputStream inputStream;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
inputStream = getContentResolver().openInputStream(fileUri);
if (inputStream == null) {
Timber.w("Input stream was null, %s", UploadActivity.class.getSimpleName());
return null;
}
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
byte[] buf = new byte[1024];
bufferedInputStream.read(buf);
do {
bufferedOutputStream.write(buf);
} while (bufferedInputStream.read(buf) != -1);
} catch (IOException exception) {
exception.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) bufferedInputStream.close();
if (bufferedOutputStream != null) bufferedOutputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
return destinationFilename;
}
private boolean deleteTempFile(String destinationFilename) {
File file = new File(destinationFilename);
return file.delete();
}
private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener { private class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
private ArrayList<UploadCategory> parentCategories, childCategories; private ArrayList<UploadCategory> parentCategories, childCategories;
@ -534,10 +510,12 @@ public class UploadActivity extends BaseActivity {
categoriesSpinners.addView(subSpinner); categoriesSpinners.addView(subSpinner);
//Sets bundle selection //Sets bundle selection
if (bundleCategory != null && viewIndex < bundleCategory.size()) { if (bundleCategory != null && !bundleCategory.isEmpty()) {
int bundleSelectionIndex = -1, currentIndex = 0; int bundleSelectionIndex = -1, currentIndex = 0;
for (UploadCategory category : parentCategories) {
if (category.getCategoryTitle().contains(bundleCategory.get(viewIndex))) { for (UploadCategory category : childCategories) {
if (bundleCategory.get(0).contains(category.getCategoryTitle()
.replace("-", "").trim())) {
bundleSelectionIndex = currentIndex; bundleSelectionIndex = currentIndex;
break; break;
} }
@ -546,6 +524,7 @@ public class UploadActivity extends BaseActivity {
if (bundleSelectionIndex != -1) { if (bundleSelectionIndex != -1) {
subSpinner.setSelection(bundleSelectionIndex, true); subSpinner.setSelection(bundleSelectionIndex, true);
bundleCategory.remove(0);
} }
} }
} }
@ -627,8 +606,9 @@ public class UploadActivity extends BaseActivity {
//Sets bundle selection //Sets bundle selection
if (bundleCategory != null) { if (bundleCategory != null) {
int bundleSelectionIndex = -1, currentIndex = 0; int bundleSelectionIndex = -1, currentIndex = 0;
for (UploadCategory category : uploadRootCategories) { for (UploadCategory category : uploadRootCategories) {
if (category.getCategoryTitle().contains(bundleCategory.get(0))) { //TODO fix .contains, always false if (bundleCategory.get(0).contains(category.getCategoryTitle())) {
bundleSelectionIndex = currentIndex; bundleSelectionIndex = currentIndex;
break; break;
} }
@ -637,6 +617,7 @@ public class UploadActivity extends BaseActivity {
if (bundleSelectionIndex != -1) { if (bundleSelectionIndex != -1) {
rootCategorySpinner.setSelection(bundleSelectionIndex, true); rootCategorySpinner.setSelection(bundleSelectionIndex, true);
bundleCategory.remove(0);
} }
} }

14
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadFieldsBuilderActivity.java

@ -96,24 +96,23 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity {
semesterChooserLinear = findViewById(R.id.upload_fields_builder_choose_semester); semesterChooserLinear = findViewById(R.id.upload_fields_builder_choose_semester);
semesterRadio = findViewById(R.id.upload_fields_builder_semester_radio_group); semesterRadio = findViewById(R.id.upload_fields_builder_semester_radio_group);
semesterRadio.check(Integer.parseInt(semester) % 2 == 0
? R.id.upload_fields_builder_radio_button_jun
: R.id.upload_fields_builder_radio_button_feb);
year = findViewById(R.id.upload_fields_builder_year); year = findViewById(R.id.upload_fields_builder_year);
year.addTextChangedListener(customYearWatcher); year.addTextChangedListener(customYearWatcher);
typeRadio = findViewById(R.id.upload_fields_builder_type_radio_group); typeRadio = findViewById(R.id.upload_fields_builder_type_radio_group);
typeRadio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { typeRadio.setOnCheckedChangeListener((group, checkedId) -> {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.upload_fields_builder_radio_button_notes) { if (checkedId == R.id.upload_fields_builder_radio_button_notes) {
semesterChooserLinear.setVisibility(View.GONE); semesterChooserLinear.setVisibility(View.GONE);
} else { } else {
semesterChooserLinear.setVisibility(View.VISIBLE); semesterChooserLinear.setVisibility(View.VISIBLE);
} }
}
}); });
findViewById(R.id.upload_fields_builder_submit).setOnClickListener(new View.OnClickListener() { findViewById(R.id.upload_fields_builder_submit).setOnClickListener(view -> {
@Override
public void onClick(View view) {
int typeId = typeRadio.getCheckedRadioButtonId(), int typeId = typeRadio.getCheckedRadioButtonId(),
semesterId = semesterRadio.getCheckedRadioButtonId(); semesterId = semesterRadio.getCheckedRadioButtonId();
if (typeId == -1) { if (typeId == -1) {
@ -133,7 +132,6 @@ public class UploadFieldsBuilderActivity extends AppCompatActivity {
returnIntent.putExtra(RESULT_DESCRIPTION, buildDescription()); returnIntent.putExtra(RESULT_DESCRIPTION, buildDescription());
setResult(Activity.RESULT_OK, returnIntent); setResult(Activity.RESULT_OK, returnIntent);
finish(); finish();
}
}); });
} }

179
app/src/main/java/gr/thmmy/mthmmy/activities/upload/UploadsHelper.java

@ -0,0 +1,179 @@
package gr.thmmy.mthmmy.activities.upload;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import timber.log.Timber;
class UploadsHelper {
private static final int DEFAULT_MIN_WIDTH_QUALITY = 400;
private static final String TEMP_IMAGE_NAME = "tempUploadFile.jpg";
@NonNull
static String filenameFromUri(Context context, Uri uri) {
String filename = null;
if (uri.getScheme().equals("content")) {
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
}
}
if (filename == null) {
filename = uri.getPath();
int cut = filename.lastIndexOf('/');
if (cut != -1) {
filename = filename.substring(cut + 1);
}
}
return filename;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Nullable
static String createTempFile(Context context, Uri fileUri, String newFilename) {
String oldFilename = filenameFromUri(context, fileUri);
String fileExtension = oldFilename.substring(oldFilename.indexOf("."));
String destinationFilename = android.os.Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads" + File.separatorChar + newFilename + fileExtension;
File tempDirectory = new File(android.os.Environment.getExternalStorageDirectory().getPath() +
File.separatorChar + "~tmp_mThmmy_uploads");
if (!tempDirectory.exists()) {
if (!tempDirectory.mkdirs()) {
Timber.w("Temporary directory build returned false in %s", UploadActivity.class.getSimpleName());
Toast.makeText(context, "Couldn't create temporary directory", Toast.LENGTH_SHORT).show();
return null;
}
}
InputStream inputStream;
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(fileUri);
if (inputStream == null) {
Timber.w("Input stream was null, %s", UploadActivity.class.getSimpleName());
return null;
}
bufferedInputStream = new BufferedInputStream(inputStream);
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
byte[] buf = new byte[1024];
bufferedInputStream.read(buf);
do {
bufferedOutputStream.write(buf);
} while (bufferedInputStream.read(buf) != -1);
} catch (IOException exception) {
exception.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) bufferedInputStream.close();
if (bufferedOutputStream != null) bufferedOutputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
return destinationFilename;
}
static File getTempFile(Context context) {
File imageFile = new File(context.getExternalCacheDir(), TEMP_IMAGE_NAME);
//noinspection ResultOfMethodCallIgnored
imageFile.getParentFile().mkdirs();
return imageFile;
}
static boolean deleteTempFile(String destinationFilename) {
File file = new File(destinationFilename);
return file.delete();
}
/**
* Resize to avoid using too much memory loading big images (e.g.: 2560*1920)
**/
static Bitmap getImageResized(Context context, Uri selectedImage) {
Bitmap bm;
int[] sampleSizes = new int[]{5, 3, 2, 1};
int i = 0;
do {
bm = decodeBitmap(context, selectedImage, sampleSizes[i]);
i++;
} while (bm.getWidth() < DEFAULT_MIN_WIDTH_QUALITY && i < sampleSizes.length);
return bm;
}
private static Bitmap decodeBitmap(Context context, Uri theUri, int sampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
AssetFileDescriptor fileDescriptor = null;
try {
fileDescriptor = context.getContentResolver().openAssetFileDescriptor(theUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
assert fileDescriptor != null;
return BitmapFactory.decodeFileDescriptor(
fileDescriptor.getFileDescriptor(), null, options);
}
static int getRotation(Context context, Uri imageUri) {
int rotation = 0;
try {
context.getContentResolver().notifyChange(imageUri, null);
ExifInterface exif = new ExifInterface(imageUri.getPath());
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
rotation = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotation = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotation = 90;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return rotation;
}
static Bitmap rotate(Bitmap bm, int rotation) {
if (rotation != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
}
return bm;
}
}

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

@ -45,6 +45,7 @@ public class Post {
private final String numberOfPosts; private final String numberOfPosts;
private final String personalText; private final String personalText;
private final int numberOfStars; private final int numberOfStars;
private final boolean isUserMentionedInPost;
// Suppresses default constructor // Suppresses default constructor
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -70,6 +71,7 @@ public class Post {
postURL = null; postURL = null;
postDeleteURL = null; postDeleteURL = null;
postEditURL = null; postEditURL = null;
isUserMentionedInPost = false;
postType = -1; postType = -1;
} }
@ -102,7 +104,8 @@ public class Post {
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts , @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor , @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL , @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, int postType) { , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; this.author = author;
@ -125,6 +128,7 @@ public class Post {
this.postURL = postURL; this.postURL = postURL;
this.postDeleteURL = postDeleteURL; this.postDeleteURL = postDeleteURL;
this.postEditURL = postEditURL; this.postEditURL = postEditURL;
this.isUserMentionedInPost = isUserMentionedInPost;
this.postType = postType; this.postType = postType;
} }
@ -148,7 +152,8 @@ public class Post {
public Post(@Nullable String thumbnailUrl, String author, String subject, String content public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor , int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL , @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit, String postURL
, @Nullable String postDeleteURL, @Nullable String postEditURL, int postType) { , @Nullable String postDeleteURL, @Nullable String postEditURL, boolean isUserMentionedInPost
, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; this.author = author;
@ -171,12 +176,13 @@ public class Post {
this.postURL = postURL; this.postURL = postURL;
this.postDeleteURL = postDeleteURL; this.postDeleteURL = postDeleteURL;
this.postEditURL = postEditURL; this.postEditURL = postEditURL;
this.isUserMentionedInPost = isUserMentionedInPost;
this.postType = postType; this.postType = postType;
} }
public static Post newQuickReply() { public static Post newQuickReply() {
return new Post(null, null, null, null, 0, 0, null, return new Post(null, null, null, null, 0, 0, null,
0, null, null, null, null, null, TYPE_QUICK_REPLY); 0, null, null, null, null, null, false, TYPE_QUICK_REPLY);
} }
//Getters //Getters
@ -390,6 +396,10 @@ public class Post {
return postType; return postType;
} }
public boolean isUserMentionedInPost() {
return isUserMentionedInPost;
}
public void setPostType(int postType) { public void setPostType(int postType) {
this.postType = postType; this.postType = postType;
} }

15
app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java

@ -4,13 +4,14 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
/** /**
* Extends FloatingActionButton's behavior so the button will hide when scrolling down and show * Extends FloatingActionButton's behavior so the button will hide when scrolling down and show
* otherwise. * otherwise. It also lifts the {@link FloatingActionButton} when a {@link Snackbar} is shown.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> { public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
@ -48,4 +49,16 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
child.show(); child.show();
} }
} }
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
} }

17
app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java

@ -4,6 +4,7 @@ import android.animation.Animator;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -13,7 +14,9 @@ import android.view.ViewPropertyAnimator;
/** /**
* Extends LinearLayout's behavior. Used for bottom navigation bar. * Extends LinearLayout's behavior. Used for bottom navigation bar.
* <p>When a nested ScrollView is scrolled down, the view will disappear. * <p>When a nested ScrollView is scrolled down, the view will disappear.
* When the ScrollView is scrolled back up, the view will reappear.</p> * When the ScrollView is scrolled back up, the view will reappear. It also pushes the
* {@link android.widget.LinearLayout} up when a {@link Snackbar} is shown
* </p>
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View> { public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View> {
@ -111,4 +114,16 @@ public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View>
animator.start(); animator.start();
} }
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
} }

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

@ -6,6 +6,8 @@ import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import timber.log.Timber;
/** /**
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner * This class consists exclusively of static classes (enums) and methods (excluding methods of inner
* classes). It can be used to resolve a page's language and state or fix embedded videos html code. * classes). It can be used to resolve a page's language and state or fix embedded videos html code.
@ -166,4 +168,22 @@ public class ParseHelpers {
} }
return fixed; return fixed;
} }
/**
* Method that extracts the base URL from a topic's page URL. For example a topic with url similar to
* "https://www.thmmy.gr/smf/index.php?topic=1.15;topicseen" or
* "https://www.thmmy.gr/smf/index.php?topic=1.msg1#msg1"
* has the base url "https://www.thmmy.gr/smf/index.php?topic=1"
*
* @param topicURL a topic's page URL
* @return the base URL of the given topic
*/
public static String getBaseURL(String topicURL) {
if (topicURL.substring(0, topicURL.lastIndexOf(".")).contains("topic="))
return topicURL.substring(0, topicURL.lastIndexOf("."));
else {
Timber.wtf(new ParseException("Could not parse base URL of topic"));
return "";
}
}
} }

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

@ -21,6 +21,7 @@ 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.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted, public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted,
PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished { PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished {
@ -54,44 +55,58 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
private EditTask.EditTaskCallbacks editTaskCallbacks; private EditTask.EditTaskCallbacks editTaskCallbacks;
private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks; private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks;
private MutableLiveData<TopicTaskResult> topicTaskResult = new MutableLiveData<>(); /**
* Holds the value (index) of the page to be requested when a user interaction with bottom
* navigation bar occurs, aka the value that the page indicator shows
*/
private MutableLiveData<Integer> pageIndicatorIndex = new MutableLiveData<>();
private MutableLiveData<String> replyPageUrl = new MutableLiveData<>();
private MutableLiveData<Integer> pageTopicId = new MutableLiveData<>();
private MutableLiveData<String> topicTitle = new MutableLiveData<>();
private MutableLiveData<ArrayList<Post>> postsList = new MutableLiveData<>();
private MutableLiveData<Integer> focusedPostIndex = new MutableLiveData<>();
private MutableLiveData<TopicTask.ResultCode> topicTaskResultCode = new MutableLiveData<>();
private MutableLiveData<String> topicTreeAndMods = new MutableLiveData<>();
private MutableLiveData<String> topicViewers = new MutableLiveData<>();
private String topicUrl;
private int currentPageIndex;
private int pageCount;
private MutableLiveData<PrepareForReplyResult> prepareForReplyResult = new MutableLiveData<>(); private MutableLiveData<PrepareForReplyResult> prepareForReplyResult = new MutableLiveData<>();
private MutableLiveData<PrepareForEditResult> prepareForEditResult = new MutableLiveData<>(); private MutableLiveData<PrepareForEditResult> prepareForEditResult = new MutableLiveData<>();
private String firstTopicUrl;
public void initialLoad(String pageUrl) {
firstTopicUrl = pageUrl;
currentTopicTask = new TopicTask(topicTaskObserver, this);
currentTopicTask.execute(pageUrl);
}
public void loadUrl(String pageUrl) { public void loadUrl(String pageUrl) {
stopLoading(); stopLoading();
topicUrl = pageUrl;
currentTopicTask = new TopicTask(topicTaskObserver, this); currentTopicTask = new TopicTask(topicTaskObserver, this);
currentTopicTask.execute(pageUrl); currentTopicTask.execute(pageUrl);
} }
public void reloadPage() { public void reloadPage() {
if (topicTaskResult.getValue() == null) if (topicUrl == null) throw new NullPointerException("No topic task has been requested yet!");
throw new NullPointerException("No topic task has finished yet!"); loadUrl(topicUrl);
loadUrl(topicTaskResult.getValue().getLastPageLoadAttemptedUrl());
} }
public void changePage(int pageRequested) { public void performPageChange() {
if (topicTaskResult.getValue() == null) if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!"); throw new NullPointerException("No page has been loaded yet!");
if (pageRequested != topicTaskResult.getValue().getCurrentPageIndex() - 1) int pageRequested = pageIndicatorIndex.getValue() - 1;
loadUrl(topicTaskResult.getValue().getPagesUrls().get(pageRequested)); if (pageRequested != currentPageIndex - 1) {
loadUrl(ParseHelpers.getBaseURL(topicUrl) + "." + String.valueOf(pageRequested * 15));
pageIndicatorIndex.setValue(pageRequested + 1);
} else {
stopLoading();
}
} }
public void prepareForReply() { public void prepareForReply() {
if (topicTaskResult.getValue() == null) if (replyPageUrl.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!"); throw new NullPointerException("Topic task has not finished yet!");
stopLoading(); stopLoading();
changePage(topicTaskResult.getValue().getPageCount() - 1); setPageIndicatorIndex(pageCount, true);
currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this, currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this,
topicTaskResult.getValue().getReplyPageUrl()); replyPageUrl.getValue());
currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0])); currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0]));
} }
@ -116,11 +131,11 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public void prepareForEdit(int position, String postEditURL) { public void prepareForEdit(int position, String postEditURL) {
if (topicTaskResult.getValue() == null) if (replyPageUrl.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!"); throw new NullPointerException("Topic task has not finished yet!");
stopLoading(); stopLoading();
currentPrepareForEditTask = new PrepareForEditTask(prepareForEditCallbacks, this, position, currentPrepareForEditTask = new PrepareForEditTask(prepareForEditCallbacks, this, position,
topicTaskResult.getValue().getReplyPageUrl()); replyPageUrl.getValue());
currentPrepareForEditTask.execute(postEditURL); currentPrepareForEditTask.execute(postEditURL);
} }
@ -140,6 +155,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
public void stopLoading() { public void stopLoading() {
if (currentTopicTask != null && currentTopicTask.getStatus() == AsyncTask.Status.RUNNING) { if (currentTopicTask != null && currentTopicTask.getStatus() == AsyncTask.Status.RUNNING) {
currentTopicTask.cancel(true); currentTopicTask.cancel(true);
pageIndicatorIndex.setValue(currentPageIndex);
topicTaskObserver.onTopicTaskCancelled(); topicTaskObserver.onTopicTaskCancelled();
} }
if (currentPrepareForEditTask != null && currentPrepareForEditTask.getStatus() == AsyncTask.Status.RUNNING) { if (currentPrepareForEditTask != null && currentPrepareForEditTask.getStatus() == AsyncTask.Status.RUNNING) {
@ -157,30 +173,108 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
// callbacks for viewmodel // callbacks for viewmodel
@Override @Override
public void onTopicTaskCompleted(TopicTaskResult result) { public void onTopicTaskCompleted(TopicTaskResult result) {
topicTaskResult.setValue(result);
if (result.getResultCode() == TopicTask.ResultCode.SUCCESS) { if (result.getResultCode() == TopicTask.ResultCode.SUCCESS) {
currentPageIndex = result.getCurrentPageIndex();
pageCount = result.getPageCount();
topicTreeAndMods.setValue(result.getTopicTreeAndMods());
topicViewers.setValue(result.getTopicViewers());
pageTopicId.setValue(result.getLoadedPageTopicId());
replyPageUrl.setValue(result.getReplyPageUrl());
topicTitle.setValue(result.getTopicTitle());
pageIndicatorIndex.setValue(result.getCurrentPageIndex());
postsList.setValue(result.getNewPostsList());
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++) {
isUserExtraInfoVisibile.add(false); isUserExtraInfoVisibile.add(false);
} }
} }
topicTaskResultCode.setValue(result.getResultCode());
} }
@Override @Override
public void onPrepareForReplyFinished(PrepareForReplyResult result) { public void onPrepareForReplyFinished(PrepareForReplyResult result) {
writingReply = true;
prepareForReplyResult.setValue(result); prepareForReplyResult.setValue(result);
} }
@Override @Override
public void onPrepareEditFinished(PrepareForEditResult result, int position) { public void onPrepareEditFinished(PrepareForEditResult result, int position) {
editingPost = true;
postBeingEditedPosition = position; postBeingEditedPosition = position;
prepareForEditResult.setValue(result); prepareForEditResult.setValue(result);
} }
public void incrementPageRequestValue(int step, boolean changePage) {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex <= pageCount - step) {
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() + step);
} else
pageIndicatorIndex.setValue(pageCount);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) performPageChange();
}
public void decrementPageRequestValue(int step, boolean changePage) {
if (pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = pageIndicatorIndex.getValue();
if (oldIndicatorIndex > step) {
pageIndicatorIndex.setValue(pageIndicatorIndex.getValue() - step);
} else
pageIndicatorIndex.setValue(1);
if (changePage && oldIndicatorIndex != pageIndicatorIndex.getValue()) performPageChange();
}
public void setPageIndicatorIndex(int pageIndicatorIndex, boolean changePage) {
if (this.pageIndicatorIndex.getValue() == null)
throw new NullPointerException("No page has been loaded yet!");
int oldIndicatorIndex = this.pageIndicatorIndex.getValue();
this.pageIndicatorIndex.setValue(pageIndicatorIndex);
if (changePage && oldIndicatorIndex != this.pageIndicatorIndex.getValue()) performPageChange();
}
// <-------------Just getters, setters and helper methods below here----------------> // <-------------Just getters, setters and helper methods below here---------------->
public MutableLiveData<String> getTopicViewers() {
return topicViewers;
}
public MutableLiveData<String> getTopicTreeAndMods() {
return topicTreeAndMods;
}
public MutableLiveData<TopicTask.ResultCode> getTopicTaskResultCode() {
return topicTaskResultCode;
}
public MutableLiveData<Integer> getFocusedPostIndex() {
return focusedPostIndex;
}
public MutableLiveData<ArrayList<Post>> getPostsList() {
return postsList;
}
public MutableLiveData<String> getReplyPageUrl() {
return replyPageUrl;
}
public MutableLiveData<Integer> getPageTopicId() {
return pageTopicId;
}
public MutableLiveData<String> getTopicTitle() {
return topicTitle;
}
public String getTopicUrl() {
return topicUrl;
}
public MutableLiveData<Integer> getPageIndicatorIndex() {
return pageIndicatorIndex;
}
public boolean isUserExtraInfoVisible(int position) { public boolean isUserExtraInfoVisible(int position) {
return isUserExtraInfoVisibile.get(position); return isUserExtraInfoVisibile.get(position);
} }
@ -228,10 +322,6 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
this.prepareForReplyCallbacks = prepareForReplyCallbacks; this.prepareForReplyCallbacks = prepareForReplyCallbacks;
} }
public MutableLiveData<TopicTaskResult> getTopicTaskResult() {
return topicTaskResult;
}
public MutableLiveData<PrepareForReplyResult> getPrepareForReplyResult() { public MutableLiveData<PrepareForReplyResult> getPrepareForReplyResult() {
return prepareForReplyResult; return prepareForReplyResult;
} }
@ -253,7 +343,7 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public boolean canReply() { public boolean canReply() {
return topicTaskResult.getValue() != null && topicTaskResult.getValue().getReplyPageUrl() != null; return replyPageUrl.getValue() != null;
} }
public boolean isWritingReply() { public boolean isWritingReply() {
@ -264,40 +354,14 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
this.writingReply = writingReply; this.writingReply = writingReply;
} }
public String getBaseUrl() {
if (topicTaskResult.getValue() != null) {
return topicTaskResult.getValue().getBaseUrl();
} else {
return "";
}
}
public String getTopicUrl() {
if (topicTaskResult.getValue() != null) {
return topicTaskResult.getValue().getLastPageLoadAttemptedUrl();
} else {
// topic task has not finished yet (log? disable menu button until load is finished?)
return firstTopicUrl;
}
}
public String getTopicTitle() {
if (topicTaskResult.getValue() == null)
throw new NullPointerException("Topic task has not finished yet!");
return topicTaskResult.getValue().getTopicTitle();
}
public int getCurrentPageIndex() { public int getCurrentPageIndex() {
if (topicTaskResult.getValue() == null) if (currentPageIndex == 0) throw new NullPointerException("No page has been loaded yet!");
throw new NullPointerException("No page has been loaded yet!"); return currentPageIndex;
return topicTaskResult.getValue().getCurrentPageIndex();
} }
public int getPageCount() { public int getPageCount() {
if (topicTaskResult.getValue() == null) if (pageCount == 0) throw new NullPointerException("No page has been loaded yet!");
throw new NullPointerException("No page has been loaded yet!"); return pageCount;
return topicTaskResult.getValue().getPageCount();
} }
public String getPostBeingEditedText() { public String getPostBeingEditedText() {
@ -307,10 +371,8 @@ public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTa
} }
public String getBuildedQuotes() { public String getBuildedQuotes() {
if (prepareForReplyResult.getValue() != null) { if (prepareForReplyResult.getValue() == null)
throw new NullPointerException("Reply preparation was not found");
return prepareForReplyResult.getValue().getBuildedQuotes(); return prepareForReplyResult.getValue().getBuildedQuotes();
} else {
return "";
}
} }
} }

13
app/src/main/res/drawable/mention_card.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/card_background"/>
<stroke
android:width="1dip"
android:color="@color/mention_color"/>
<corners android:radius="5dip"/>
<padding
android:bottom="0dip"
android:left="0dip"
android:right="0dip"
android:top="0dip"/>
</shape>

10
app/src/main/res/layout/activity_topic.xml

@ -47,6 +47,16 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView> </android.support.v7.widget.RecyclerView>
<TextView
android:id="@+id/error_textview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/network_error_retry_prompt"
android:textSize="32sp"
android:textColor="@color/white"
android:gravity="center"
android:visibility="gone" />
<LinearLayout <LinearLayout
android:id="@+id/bottom_navigation_bar" android:id="@+id/bottom_navigation_bar"
android:layout_width="match_parent" android:layout_width="match_parent"

8
app/src/main/res/layout/activity_topic_overflow_menu.xml

@ -19,7 +19,7 @@
android:textColor="@color/primary_text" /> android:textColor="@color/primary_text" />
<TextView <TextView
android:id="@+id/delete_post" android:id="@+id/edit_post"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
@ -29,11 +29,11 @@
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingTop="6dp" android:paddingTop="6dp"
android:text="@string/post_delete_button" android:text="@string/post_edit_button"
android:textColor="@color/primary_text" /> android:textColor="@color/primary_text" />
<TextView <TextView
android:id="@+id/edit_post" android:id="@+id/delete_post"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="35dp" android:layout_height="35dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
@ -43,7 +43,7 @@
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingTop="6dp" android:paddingTop="6dp"
android:text="@string/post_edit_button" android:text="@string/post_delete_button"
android:textColor="@color/primary_text" /> android:textColor="@color/primary_text" />
</LinearLayout> </LinearLayout>

1
app/src/main/res/values/colors.xml

@ -17,6 +17,7 @@
<color name="card_background">#3C3F41</color> <color name="card_background">#3C3F41</color>
<color name="divider">#8B8B8B</color> <color name="divider">#8B8B8B</color>
<color name="link_color">#FF9800</color> <color name="link_color">#FF9800</color>
<color name="mention_color">#FAA61A</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="iron">#CCCCCC</color> <color name="iron">#CCCCCC</color>

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

@ -55,6 +55,10 @@
<string name="subject">Subject&#8230;</string> <string name="subject">Subject&#8230;</string>
<string name="submit">Submit</string> <string name="submit">Submit</string>
<string name="post_message">Message&#8230;</string> <string name="post_message">Message&#8230;</string>
<string name="network_error_retry_prompt">Could not connect to thmmy.gr \n\n Tap to retry</string>
<string name="generic_network_error">Network error</string>
<string name="retry">retry</string>
<string name="unauthorized_topic_error">This topic is either missing or off limits to you</string>
<!--Profile Activity--> <!--Profile Activity-->
<string name="username">Username</string> <string name="username">Username</string>

Loading…
Cancel
Save