diff --git a/app/build.gradle b/app/build.gradle
index cd627a53..dd59ec42 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -25,6 +25,11 @@ android {
archivesBaseName = archivesBaseName + "-$date"
}
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
@@ -37,7 +42,7 @@ dependencies {
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.google.firebase:firebase-core:16.0.1'
- implementation 'com.google.firebase:firebase-messaging:17.0.0'
+ implementation 'com.google.firebase:firebase-messaging:17.1.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.4'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
@@ -56,6 +61,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.0'
implementation 'net.gotev:uploadservice:3.4.2'
implementation 'net.gotev:uploadservice-okhttp:3.4.2'
+ implementation 'android.arch.lifecycle:extensions:1.1.1'
}
apply plugin: 'com.google.gms.google-services'
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
index cc08f17d..88c2f8a3 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
@@ -11,11 +11,11 @@ import timber.log.Timber;
/**
* This is a utility class containing a collection of static methods to help with topic replying.
*/
-class Posting {
+public class Posting {
/**
* {@link REPLY_STATUS} enum defines the different possible outcomes of a topic reply request.
*/
- enum REPLY_STATUS {
+ public enum REPLY_STATUS {
/**
* The request was successful
*/
@@ -54,7 +54,7 @@ class Posting {
* @return a {@link REPLY_STATUS} that describes the response status
* @throws IOException method relies to {@link org.jsoup.Jsoup#parse(String)}
*/
- static REPLY_STATUS replyStatus(Response response) throws IOException {
+ public static REPLY_STATUS replyStatus(Response response) throws IOException {
if (response.code() == 404) return REPLY_STATUS.NOT_FOUND;
if (response.code() < 200 || response.code() >= 400) return REPLY_STATUS.OTHER_ERROR;
String finalUrl = response.request().url().toString();
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
index 855f7045..3da39de4 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
@@ -2,27 +2,20 @@ package gr.thmmy.mthmmy.activities.topic;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
+import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.RecyclerView;
-import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.URLSpan;
-import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -35,40 +28,26 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Selector;
-
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
-import gr.thmmy.mthmmy.activities.board.BoardActivity;
-import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
-import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
+import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply;
+import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CustomLinearLayoutManager;
-import gr.thmmy.mthmmy.utils.parsing.ParseException;
-import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
+import gr.thmmy.mthmmy.utils.HTMLUtils;
+import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
-import okhttp3.MultipartBody;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
import timber.log.Timber;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
-import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
-import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
-import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
-import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
-import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
import static gr.thmmy.mthmmy.services.NotificationService.NEW_POST_TAG;
/**
@@ -78,7 +57,9 @@ import static gr.thmmy.mthmmy.services.NotificationService.NEW_POST_TAG;
* key {@link #BUNDLE_TOPIC_TITLE} for faster title rendering.
*/
@SuppressWarnings("unchecked")
-public class TopicActivity extends BaseActivity {
+public class TopicActivity extends BaseActivity implements TopicTask.TopicTaskObserver,
+ DeleteTask.DeleteTaskCallbacks, ReplyTask.ReplyTaskCallbacks, PrepareForEditTask.PrepareForEditCallbacks,
+ EditTask.EditTaskCallbacks, PrepareForReply.PrepareForReplyCallbacks, TopicAdapter.OnPostFocusChangeListener {
//Activity's variables
/**
* The key to use when putting topic's url String to {@link TopicActivity}'s Bundle.
@@ -88,80 +69,18 @@ public class TopicActivity extends BaseActivity {
* The key to use when putting topic's title String to {@link TopicActivity}'s Bundle.
*/
public static final String BUNDLE_TOPIC_TITLE = "TOPIC_TITLE";
- private static TopicTask topicTask;
private MaterialProgressBar progressBar;
private TextView toolbarTitle;
- /**
- * 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 static String base_url = "";
- /**
- * 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, after topic's parsing
- * is done, it gets the value of {@link #parsedTitle} if bundle title and parsed title differ.
- */
- private String topicTitle;
- /**
- * Holds this topic's title as parsed from the html source. If this (parsed) title is different
- * than the one that came with activity's bundle then the parsed title is preferred over the
- * bundle one and gets rendered in the toolbar.
- */
- private String parsedTitle;
- private String topicPageUrl;
private RecyclerView recyclerView;
- /**
- * Holds the url of this page
- */
- private String loadedPageUrl = "";
- /**
- * Holds the topicId of this page
- */
- private int loadedPageTopicId = -1;
- /**
- * Becomes true after user has posted in this topic and the page is being reloaded and false
- * when topic's reloading is done
- */
- private boolean reloadingPage = false;
//Posts related
private TopicAdapter topicAdapter;
/**
* Holds a list of this topic's posts
*/
private ArrayList postsList;
- /**
- * Gets assigned to {@link #postFocus} when there is no post focus information in the url
- */
- private static final int NO_POST_FOCUS = -1;
- /**
- * Holds the index of the post that has focus
- */
- private int postFocus = NO_POST_FOCUS;
- /**
- * Holds the position in the {@link #postsList} of the post with focus
- */
- private static int postFocusPosition = 0;
//Reply related
private FloatingActionButton replyFAB;
- /**
- * Holds this topic's reply url
- */
- private String replyPageUrl = null;
//Topic's pages related
- /**
- * Holds current page's index (starting from 1, not 0)
- */
- private int thisPage = 1;
- /**
- * Holds this topic's number of pages
- */
- private int numberOfPages = 1;
- /**
- * Holds a list of this topic's pages urls
- */
- private final SparseArray pagesUrls = new SparseArray<>();
//Page select related
/**
* Used for handling bottom navigation bar's buttons long click user interactions
@@ -196,12 +115,7 @@ public class TopicActivity extends BaseActivity {
private TextView pageIndicator;
private ImageButton nextPage;
private ImageButton lastPage;
-
- //Topic's info related
- private SpannableStringBuilder topicTreeAndMods = new SpannableStringBuilder("Loading..."),
- topicViewers = new SpannableStringBuilder("Loading...");
-
- boolean includeAppSignaturePreference = true;
+ private TopicViewModel viewModel;
//Fix for vector drawables on android <21
static {
@@ -215,9 +129,17 @@ public class TopicActivity extends BaseActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_topic);
+ viewModel = ViewModelProviders.of(this).get(TopicViewModel.class);
+ viewModel.setTopicTaskObserver(this);
+ viewModel.setDeleteTaskCallbacks(this);
+ viewModel.setReplyFinishListener(this);
+ viewModel.setPrepareForEditCallbacks(this);
+ viewModel.setEditTaskCallbacks(this);
+ viewModel.setPrepareForReplyCallbacks(this);
+
Bundle extras = getIntent().getExtras();
- topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
- topicPageUrl = extras.getString(BUNDLE_TOPIC_URL);
+ String topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
+ String topicPageUrl = extras.getString(BUNDLE_TOPIC_URL);
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
@@ -227,12 +149,6 @@ public class TopicActivity extends BaseActivity {
}
topicPageUrl = ThmmyPage.sanitizeTopicUrl(topicPageUrl);
-
- if (sessionManager.isLoggedIn()) {
- SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- includeAppSignaturePreference = sharedPrefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true);
- }
-
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl), true);
//Initializes graphics
@@ -257,35 +173,21 @@ public class TopicActivity extends BaseActivity {
recyclerView = findViewById(R.id.topic_recycler_view);
recyclerView.setHasFixedSize(true);
- recyclerView.setOnTouchListener(
- new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- v.performClick();
- return topicTask != null && topicTask.getStatus() == AsyncTask.Status.RUNNING;
- }
- }
- );
//LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(
- getApplicationContext(), loadedPageUrl);
+ getApplicationContext(), topicPageUrl);
recyclerView.setLayoutManager(layoutManager);
- topicAdapter = new TopicAdapter(this, postsList, base_url, topicTask);
+ topicAdapter = new TopicAdapter(this, postsList);
recyclerView.setAdapter(topicAdapter);
replyFAB = findViewById(R.id.topic_fab);
- replyFAB.setEnabled(false);
+ replyFAB.hide();
bottomNavBar = findViewById(R.id.bottom_navigation_bar);
if (!sessionManager.isLoggedIn()) replyFAB.hide();
else {
- replyFAB.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (sessionManager.isLoggedIn()) {
- PrepareForReply prepareForReply = new PrepareForReply();
- prepareForReply.execute(topicAdapter.getToQuoteList());
- }
- }
+ replyFAB.setOnClickListener(view -> {
+ if (sessionManager.isLoggedIn())
+ viewModel.prepareForReply();
});
}
@@ -300,11 +202,84 @@ public class TopicActivity extends BaseActivity {
initDecrementButton(previousPage, SMALL_STEP);
initIncrementButton(nextPage, SMALL_STEP);
initIncrementButton(lastPage, LARGE_STEP);
+
paginationEnabled(false);
- //Gets posts
- topicTask = new TopicTask();
- topicTask.execute(topicPageUrl); //Attempt data parsing
+ viewModel.getTopicTaskResult().observe(this, topicTaskResult -> {
+ 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
@@ -329,11 +304,22 @@ public class TopicActivity extends BaseActivity {
LinearLayout infoDialog = (LinearLayout) inflater.inflate(R.layout.dialog_topic_info
, null);
TextView treeAndMods = infoDialog.findViewById(R.id.topic_tree_and_mods);
- treeAndMods.setText(topicTreeAndMods);
+ treeAndMods.setText(new SpannableStringBuilder("Loading..."));
treeAndMods.setMovementMethod(LinkMovementMethod.getInstance());
TextView usersViewing = infoDialog.findViewById(R.id.users_viewing);
- usersViewing.setText(topicViewers);
+ usersViewing.setText(new SpannableStringBuilder("Loading..."));
usersViewing.setMovementMethod(LinkMovementMethod.getInstance());
+ viewModel.getTopicTaskResult().observe(this, topicTaskResult -> {
+ if (topicTaskResult == null) {
+ usersViewing.setText(new SpannableStringBuilder("Loading..."));
+ treeAndMods.setText(new SpannableStringBuilder("Loading..."));
+ } else {
+ String treeAndModsString = topicTaskResult.getTopicTreeAndMods();
+ treeAndMods.setText(HTMLUtils.getSpannableFromHtml(this, treeAndModsString));
+ String topicViewersString = topicTaskResult.getTopicViewers();
+ usersViewing.setText(HTMLUtils.getSpannableFromHtml(this, topicViewersString));
+ }
+ });
builder.setView(infoDialog);
AlertDialog dialog = builder.create();
@@ -342,9 +328,9 @@ public class TopicActivity extends BaseActivity {
case R.id.menu_share:
Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
sendIntent.setType("text/plain");
- sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, topicPageUrl);
+ sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, viewModel.getTopicUrl());
startActivity(Intent.createChooser(sendIntent, "Share via"));
- return true;
+ return true; //invalidateOptionsMenu();
default:
return super.onOptionsItemSelected(item);
}
@@ -355,14 +341,21 @@ public class TopicActivity extends BaseActivity {
if (drawer.isDrawerOpen()) {
drawer.closeDrawer();
return;
- } else if (postsList != null && postsList.size() > 0 && postsList.get(postsList.size() - 1) == null) {
+ } else if (viewModel.isWritingReply()) {
postsList.remove(postsList.size() - 1);
topicAdapter.notifyItemRemoved(postsList.size());
topicAdapter.setBackButtonHidden();
- replyFAB.setVisibility(View.INVISIBLE);
- bottomNavBar.setVisibility(View.INVISIBLE);
- paginationEnabled(true);
- replyFAB.setEnabled(true);
+ viewModel.setWritingReply(false);
+ replyFAB.show();
+ bottomNavBar.setVisibility(View.VISIBLE);
+ return;
+ } else if (viewModel.isEditingPost()) {
+ postsList.get(viewModel.getPostBeingEditedPosition()).setPostType(Post.TYPE_POST);
+ topicAdapter.notifyItemChanged(viewModel.getPostBeingEditedPosition());
+ topicAdapter.setBackButtonHidden();
+ viewModel.setEditingPost(false);
+ replyFAB.show();
+ bottomNavBar.setVisibility(View.VISIBLE);
return;
}
super.onBackPressed();
@@ -373,19 +366,18 @@ public class TopicActivity extends BaseActivity {
super.onResume();
refreshTopicBookmark();
drawer.setSelection(-1);
-
- if (sessionManager.isLoggedIn()) {
- SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- includeAppSignaturePreference = sharedPrefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true);
- }
}
@Override
protected void onDestroy() {
super.onDestroy();
recyclerView.setAdapter(null);
- if (topicTask != null && topicTask.getStatus() != AsyncTask.Status.RUNNING)
- topicTask.cancel(true);
+ viewModel.stopLoading();
+ }
+
+ @Override
+ public void onPostFocusChange(int position) {
+ recyclerView.scrollToPosition(position);
}
//--------------------------------------BOTTOM NAV BAR METHODS----------------------------------
@@ -448,26 +440,23 @@ public class TopicActivity extends BaseActivity {
@SuppressLint("ClickableViewAccessibility")
private void initIncrementButton(ImageButton increment, final int step) {
// Increment once for a click
- increment.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- if (!autoIncrement && step == LARGE_STEP) {
- changePage(numberOfPages - 1);
- } else if (!autoIncrement) {
- incrementPageRequestValue(step);
- changePage(pageRequestValue - 1);
- }
+ increment.setOnClickListener(v -> {
+ if (!autoIncrement && step == LARGE_STEP) {
+ incrementPageRequestValue(viewModel.getPageCount());
+ viewModel.changePage(viewModel.getPageCount() - 1);
+ } else if (!autoIncrement) {
+ incrementPageRequestValue(step);
+ viewModel.changePage(pageRequestValue - 1);
}
});
// Auto increment for a long click
increment.setOnLongClickListener(
- new View.OnLongClickListener() {
- public boolean onLongClick(View arg0) {
- paginationDisable(arg0);
- autoIncrement = true;
- repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
- return false;
- }
+ arg0 -> {
+ paginationDisable(arg0);
+ autoIncrement = true;
+ repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
+ return false;
}
);
@@ -481,11 +470,11 @@ public class TopicActivity extends BaseActivity {
} else if (rect != null && event.getAction() == MotionEvent.ACTION_UP && autoIncrement) {
autoIncrement = false;
paginationEnabled(true);
- changePage(pageRequestValue - 1);
+ viewModel.changePage(pageRequestValue - 1);
} else if (rect != null && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false;
- decrementPageRequestValue(pageRequestValue - thisPage);
+ decrementPageRequestValue(pageRequestValue - viewModel.getCurrentPageIndex());
paginationEnabled(true);
}
}
@@ -497,26 +486,23 @@ public class TopicActivity extends BaseActivity {
@SuppressLint("ClickableViewAccessibility")
private void initDecrementButton(ImageButton decrement, final int step) {
// Decrement once for a click
- decrement.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- if (!autoDecrement && step == LARGE_STEP) {
- changePage(0);
- } else if (!autoDecrement) {
- decrementPageRequestValue(step);
- changePage(pageRequestValue - 1);
- }
+ decrement.setOnClickListener(v -> {
+ if (!autoDecrement && step == LARGE_STEP) {
+ decrementPageRequestValue(viewModel.getPageCount());
+ viewModel.changePage(0);
+ } else if (!autoDecrement) {
+ decrementPageRequestValue(step);
+ viewModel.changePage(pageRequestValue - 1);
}
});
// Auto decrement for a long click
decrement.setOnLongClickListener(
- new View.OnLongClickListener() {
- public boolean onLongClick(View arg0) {
- paginationDisable(arg0);
- autoDecrement = true;
- repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
- return false;
- }
+ arg0 -> {
+ paginationDisable(arg0);
+ autoDecrement = true;
+ repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
+ return false;
}
);
@@ -530,12 +516,12 @@ public class TopicActivity extends BaseActivity {
} else if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) {
autoDecrement = false;
paginationEnabled(true);
- changePage(pageRequestValue - 1);
+ viewModel.changePage(pageRequestValue - 1);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (rect != null &&
!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false;
- incrementPageRequestValue(thisPage - pageRequestValue);
+ incrementPageRequestValue(viewModel.getCurrentPageIndex() - pageRequestValue);
paginationEnabled(true);
}
}
@@ -545,11 +531,11 @@ public class TopicActivity extends BaseActivity {
}
private void incrementPageRequestValue(int step) {
- if (pageRequestValue < numberOfPages - step) {
+ if (pageRequestValue < viewModel.getPageCount() - step) {
pageRequestValue = pageRequestValue + step;
} else
- pageRequestValue = numberOfPages;
- pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages));
+ pageRequestValue = viewModel.getPageCount();
+ pageIndicator.setText(pageRequestValue + "/" + String.valueOf(viewModel.getPageCount()));
}
private void decrementPageRequestValue(int step) {
@@ -557,490 +543,113 @@ public class TopicActivity extends BaseActivity {
pageRequestValue = pageRequestValue - step;
else
pageRequestValue = 1;
- pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages));
+ pageIndicator.setText(pageRequestValue + "/" + String.valueOf(viewModel.getPageCount()));
}
- private void changePage(int pageRequested) {
- if (pageRequested != thisPage - 1) {
- if (topicTask != null && topicTask.getStatus() != AsyncTask.Status.RUNNING)
- topicTask.cancel(true);
+ //------------------------------------BOTTOM NAV BAR METHODS END------------------------------------
- topicTask = new TopicTask();
- topicTask.execute(pagesUrls.get(pageRequested)); //Attempt data parsing
- }
+ @Override
+ public void onTopicTaskStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
}
- //------------------------------------BOTTOM NAV BAR METHODS END------------------------------------
- private enum ResultCode {
- SUCCESS, NETWORK_ERROR, PARSING_ERROR, OTHER_ERROR, SAME_PAGE, UNAUTHORIZED
+ @Override
+ public void onTopicTaskCancelled() {
+ progressBar.setVisibility(ProgressBar.GONE);
}
+ @Override
+ public void onReplyTaskStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
+ }
- /**
- * An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of its
- * data.
- * TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String
- * parameter.
- */
- class TopicTask extends AsyncTask {
- ArrayList localPostsList;
-
- @Override
- protected void onPreExecute() {
- progressBar.setVisibility(ProgressBar.VISIBLE);
- paginationEnabled(false);
- if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false);
- }
-
- protected ResultCode doInBackground(String... strings) {
- Document document = null;
- String newPageUrl = strings[0];
-
- //Finds the index of message focus if present
- {
- postFocus = NO_POST_FOCUS;
- if (newPageUrl.contains("msg")) {
- String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
- if (tmp.contains(";"))
- postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";")));
- else if (tmp.contains("#"))
- postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#")));
- }
- }
- //Checks if the page to be loaded is the one already shown
- if (!reloadingPage && !Objects.equals(loadedPageUrl, "") && newPageUrl.contains(base_url)) {
- if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new"))
- if (thisPage == numberOfPages)
- return ResultCode.SAME_PAGE;
- if (newPageUrl.contains("msg")) {
- String tmpUrlSbstr = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
- if (tmpUrlSbstr.contains("msg"))
- tmpUrlSbstr = tmpUrlSbstr.substring(0, tmpUrlSbstr.indexOf("msg") - 1);
- int testAgainst = Integer.parseInt(tmpUrlSbstr);
- for (Post post : postsList) {
- if (post.getPostIndex() == testAgainst) {
- return ResultCode.SAME_PAGE;
- }
- }
- } else if ((Objects.equals(newPageUrl, base_url) && thisPage == 1) ||
- Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
- return ResultCode.SAME_PAGE;
- } else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null;
- if (reloadingPage) reloadingPage = !reloadingPage;
-
- loadedPageUrl = newPageUrl;
- if (strings[0].substring(0, strings[0].lastIndexOf(".")).contains("topic="))
- base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
- replyPageUrl = null;
- Request request = new Request.Builder()
- .url(newPageUrl)
- .build();
- try {
- Response response = client.newCall(request).execute();
- document = Jsoup.parse(response.body().string());
- localPostsList = parse(document);
-
- loadedPageTopicId = Integer.parseInt(ThmmyPage.getTopicId(loadedPageUrl));
-
- //Finds the position of the focused message if present
- for (int i = 0; i < localPostsList.size(); ++i) {
- if (localPostsList.get(i).getPostIndex() == postFocus) {
- postFocusPosition = i;
- break;
- }
- }
- return ResultCode.SUCCESS;
- } catch (IOException e) {
- Timber.i(e, "IO Exception");
- return ResultCode.NETWORK_ERROR;
- } catch (ParseException e) {
- if (isUnauthorized(document))
- return ResultCode.UNAUTHORIZED;
- Timber.e(e, "Parsing Error");
- return ResultCode.PARSING_ERROR;
- } catch (Exception e) {
- Timber.e(e, "Exception");
- return ResultCode.OTHER_ERROR;
- }
- }
-
- protected void onPostExecute(ResultCode parseResult) {
- switch (parseResult) {
- case SUCCESS:
- if (topicTitle == null || Objects.equals(topicTitle, "")
- || !Objects.equals(topicTitle, parsedTitle)) {
- toolbarTitle.setText(parsedTitle);
- topicTitle = parsedTitle;
- thisPageBookmark = new Bookmark(parsedTitle, Integer.toString(loadedPageTopicId), true);
- invalidateOptionsMenu();
- }
-
- if (!postsList.isEmpty()) {
- recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
- postsList.clear();
- topicAdapter.notifyItemRangeRemoved(0, postsList.size() - 1);
- }
- postsList.addAll(localPostsList);
- topicAdapter.notifyItemRangeInserted(0, postsList.size());
- topicAdapter.prepareForDelete(new DeleteTask());
- progressBar.setVisibility(ProgressBar.INVISIBLE);
-
- if (replyPageUrl == null) {
- replyFAB.hide();
- topicAdapter.resetTopic(base_url, new TopicTask(), false);
- } else topicAdapter.resetTopic(base_url, new TopicTask(), true);
-
- if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
-
- //Set current page
- pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages));
- pageRequestValue = thisPage;
-
- if (thisPage == numberOfPages) {
- NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- if (notificationManager != null)
- notificationManager.cancel(NEW_POST_TAG, loadedPageTopicId);
- }
-
- paginationEnabled(true);
- break;
- case NETWORK_ERROR:
- Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
- break;
- case SAME_PAGE:
- stopLoading();
- Toast.makeText(getBaseContext(), "That's the same page", Toast.LENGTH_SHORT).show();
- //TODO change focus
- break;
- case UNAUTHORIZED:
- stopLoading();
- 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;
- }
- }
-
- private void stopLoading() {
- progressBar.setVisibility(ProgressBar.INVISIBLE);
- if (replyPageUrl == null) {
- replyFAB.hide();
- topicAdapter.resetTopic(base_url, new TopicTask(), false);
- } else topicAdapter.resetTopic(base_url, new TopicTask(), true);
- if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
- paginationEnabled(true);
- }
-
- /**
- * All the parsing a topic needs.
- *
- * @param topic {@link Document} object containing this topic's source code
- * @see org.jsoup.Jsoup Jsoup
- */
- private ArrayList parse(Document topic) throws ParseException {
- try {
- ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
-
- //Finds topic's tree, mods and users viewing
- {
- topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html());
- topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language));
- }
-
- //Finds reply page url
- {
- Element replyButton = topic.select("a:has(img[alt=Reply])").first();
- if (replyButton == null)
- replyButton = topic.select("a:has(img[alt=Απάντηση])").first();
- if (replyButton != null) replyPageUrl = replyButton.attr("href");
- }
-
- //Finds topic title if missing
- {
- parsedTitle = topic.select("td[id=top_subject]").first().text();
- if (parsedTitle.contains("Topic:")) {
- parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7
- , parsedTitle.indexOf("(Read") - 2);
- } else {
- parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6
- , parsedTitle.indexOf("(Αναγνώστηκε") - 2);
- Timber.d("Parsed title: %s", parsedTitle);
- }
- }
-
- { //Finds current page's index
- thisPage = TopicParser.parseCurrentPageIndex(topic, language);
- }
- { //Finds number of pages
- numberOfPages = TopicParser.parseTopicNumberOfPages(topic, thisPage, language);
-
- for (int i = 0; i < numberOfPages; i++) {
- //Generate each page's url from topic's base url +".15*numberOfPage"
- pagesUrls.put(i, base_url + "." + String.valueOf(i * 15));
- }
- }
-
- return TopicParser.parseTopic(topic, language);
- } catch (Exception e) {
- throw new ParseException("Parsing failed (TopicTask)");
- }
+ @Override
+ public void onReplyTaskFinished(boolean success) {
+ View view = getCurrentFocus();
+ if (view != null) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
- private boolean isUnauthorized(Document document) {
- return document != null && document.select("body:contains(The topic or board you" +
- " are looking for appears to be either missing or off limits to you.)," +
- "body:contains(Το θέμα ή πίνακας που ψάχνετε ή δεν υπάρχει ή δεν " +
- "είναι προσβάσιμο από εσάς.)").size() > 0;
- }
+ postsList.remove(postsList.size() - 1);
+ topicAdapter.notifyItemRemoved(postsList.size());
- private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) {
- int start = strBuilder.getSpanStart(span);
- int end = strBuilder.getSpanEnd(span);
- int flags = strBuilder.getSpanFlags(span);
- ClickableSpan clickable = new ClickableSpan() {
- @Override
- public void onClick(View view) {
- ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
- if (target.is(ThmmyPage.PageCategory.BOARD)) {
- Intent intent = new Intent(getApplicationContext(), BoardActivity.class);
- Bundle extras = new Bundle();
- extras.putString(BUNDLE_BOARD_URL, span.getURL());
- extras.putString(BUNDLE_BOARD_TITLE, "");
- intent.putExtras(extras);
- intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- getApplicationContext().startActivity(intent);
- } else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
- Intent intent = new Intent(getApplicationContext(), ProfileActivity.class);
- Bundle extras = new Bundle();
- extras.putString(BUNDLE_PROFILE_URL, span.getURL());
- extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
- extras.putString(BUNDLE_PROFILE_USERNAME, "");
- intent.putExtras(extras);
- intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- getApplicationContext().startActivity(intent);
- } else if (target.is(ThmmyPage.PageCategory.INDEX))
- finish();
- }
- };
- strBuilder.setSpan(clickable, start, end, flags);
- strBuilder.removeSpan(span);
- }
+ progressBar.setVisibility(ProgressBar.GONE);
+ replyFAB.show();
+ bottomNavBar.setVisibility(View.VISIBLE);
+ viewModel.setWritingReply(false);
- private SpannableStringBuilder getSpannableFromHtml(String html) {
- CharSequence sequence;
- if (Build.VERSION.SDK_INT >= 24) {
- sequence = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
+ if (success) {
+ if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0) {
+ viewModel.loadUrl(viewModel.getBaseUrl() + "." + 2147483647);
} else {
- //noinspection deprecation
- sequence = Html.fromHtml(html);
- }
- SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
- URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
- for (URLSpan span : urls) {
- makeLinkClickable(strBuilder, span);
+ viewModel.reloadPage();
}
- return strBuilder;
+ } else {
+ Toast.makeText(TopicActivity.this, "Post failed!", Toast.LENGTH_SHORT).show();
}
}
- class PrepareForReply extends AsyncTask, Void, Boolean> {
- String numReplies, seqnum, sc, topic, buildedQuotes = "";
-
- @Override
- protected void onPreExecute() {
- changePage(numberOfPages - 1);
- progressBar.setVisibility(ProgressBar.VISIBLE);
- paginationEnabled(false);
- replyFAB.setEnabled(false);
- replyFAB.hide();
- bottomNavBar.setVisibility(View.GONE);
- }
-
- @Override
- protected Boolean doInBackground(ArrayList... quoteList) {
- Document document;
- Request request = new Request.Builder()
- .url(replyPageUrl + ";wap2")
- .build();
-
- try {
- Response response = client.newCall(request).execute();
- document = Jsoup.parse(response.body().string());
-
- numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
- seqnum = document.select("input[name=seqnum]").first().attr("value");
- sc = document.select("input[name=sc]").first().attr("value");
- topic = document.select("input[name=topic]").first().attr("value");
- } catch (IOException | Selector.SelectorParseException e) {
- Timber.e(e, "Prepare failed.");
- return false;
- }
-
- for (Integer quotePosition : quoteList[0]) {
- request = new Request.Builder()
- .url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" +
- postsList.get(quotePosition).getPostIndex() +
- ";" + "sesc=" + sc + ";xml")
- .build();
-
- try {
- Response response = client.newCall(request).execute();
- String body = response.body().string();
- buildedQuotes += body.substring(body.indexOf("") + 7, body.indexOf("
"));
- buildedQuotes += "\n\n";
- } catch (IOException | Selector.SelectorParseException e) {
- Timber.e(e, "Quote building failed.");
- return false;
- }
- }
- return true;
- }
+ @Override
+ public void onPrepareForReplyStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
+ }
- @Override
- protected void onPostExecute(Boolean result) {
- postsList.add(null);
- topicAdapter.notifyItemInserted(postsList.size());
- topicAdapter.prepareForReply(new ReplyTask(), topicTitle, numReplies, seqnum, sc,
- topic, buildedQuotes);
- recyclerView.scrollToPosition(postsList.size() - 1);
- progressBar.setVisibility(ProgressBar.GONE);
- }
+ @Override
+ public void onPrepareForReplyCancelled() {
+ progressBar.setVisibility(ProgressBar.GONE);
}
- class ReplyTask extends AsyncTask {
+ @Override
+ public void onDeleteTaskStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
+ }
- @Override
- protected void onPreExecute() {
- progressBar.setVisibility(ProgressBar.VISIBLE);
- paginationEnabled(false);
- replyFAB.setEnabled(false);
- }
+ @Override
+ public void onDeleteTaskFinished(boolean result) {
+ progressBar.setVisibility(ProgressBar.GONE);
- @Override
- protected Boolean doInBackground(String... args) {
- final String sentFrommTHMMY = includeAppSignaturePreference
- ? "\n[right][size=7pt][i]sent from [url=https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy]mTHMMY [/url][/i][/size][/right]"
- : "";
- RequestBody postBody = new MultipartBody.Builder()
- .setType(MultipartBody.FORM)
- .addFormDataPart("message", args[1] + sentFrommTHMMY)
- .addFormDataPart("num_replies", args[2])
- .addFormDataPart("seqnum", args[3])
- .addFormDataPart("sc", args[4])
- .addFormDataPart("subject", args[0])
- .addFormDataPart("topic", args[5])
- .build();
- Request post = new Request.Builder()
- .url("https://www.thmmy.gr/smf/index.php?action=post2")
- .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
- .post(postBody)
- .build();
-
- try {
- client.newCall(post).execute();
- Response response = client.newCall(post).execute();
- switch (replyStatus(response)) {
- case SUCCESSFUL:
- return true;
- case NEW_REPLY_WHILE_POSTING:
- //TODO this...
- return true;
- default:
- Timber.e("Malformed post. Request string: %s", post.toString());
- return true;
- }
- } catch (IOException e) {
- Timber.e(e, "Post failed.");
- return false;
- }
+ if (result) {
+ viewModel.reloadPage();
+ } else {
+ Toast.makeText(TopicActivity.this, "Post deleted!", Toast.LENGTH_SHORT).show();
}
+ }
- @Override
- protected void onPostExecute(Boolean result) {
- View view = getCurrentFocus();
- if (view != null) {
- InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
- }
-
- postsList.remove(postsList.size() - 1);
- topicAdapter.notifyItemRemoved(postsList.size());
-
- progressBar.setVisibility(ProgressBar.GONE);
- replyFAB.setVisibility(View.VISIBLE);
- bottomNavBar.setVisibility(View.VISIBLE);
-
- if (!result)
- Toast.makeText(TopicActivity.this, "Post failed!", Toast.LENGTH_SHORT).show();
- paginationEnabled(true);
- replyFAB.setEnabled(true);
-
- if (result) {
- topicTask = new TopicTask();
- if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0)
- topicTask.execute(base_url + "." + 2147483647);
- else {
- reloadingPage = true;
- topicTask.execute(loadedPageUrl);
- }
- }
- }
+ @Override
+ public void onPrepareEditStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
}
- class DeleteTask extends AsyncTask {
+ @Override
+ public void onPrepareEditCancelled() {
+ progressBar.setVisibility(ProgressBar.GONE);
+ }
- @Override
- protected void onPreExecute() {
- progressBar.setVisibility(ProgressBar.VISIBLE);
- paginationEnabled(false);
- replyFAB.setEnabled(false);
- }
+ @Override
+ public void onEditTaskStarted() {
+ progressBar.setVisibility(ProgressBar.VISIBLE);
+ }
- @Override
- protected Boolean doInBackground(String... args) {
- Request delete = new Request.Builder()
- .url(args[0])
- .header("User-Agent",
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
- .build();
-
- try {
- client.newCall(delete).execute();
- Response response = client.newCall(delete).execute();
- //Response response = client.newCall(delete).execute();
- switch (replyStatus(response)) {
- case SUCCESSFUL:
- return true;
- default:
- Timber.e("Something went wrong. Request string: %s", delete.toString());
- return true;
- }
- } catch (IOException e) {
- Timber.e(e, "Delete failed.");
- return false;
- }
+ @Override
+ 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);
}
- @Override
- protected void onPostExecute(Boolean result) {
- progressBar.setVisibility(ProgressBar.GONE);
- replyFAB.setVisibility(View.VISIBLE);
- bottomNavBar.setVisibility(View.VISIBLE);
-
- if (!result)
- Toast.makeText(TopicActivity.this, "Post deleted!", Toast.LENGTH_SHORT).show();
- paginationEnabled(true);
- replyFAB.setEnabled(true);
+ postsList.get(position).setPostType(Post.TYPE_POST);
+ topicAdapter.notifyItemChanged(position);
+ viewModel.setEditingPost(false);
+ progressBar.setVisibility(ProgressBar.GONE);
+ replyFAB.show();
+ bottomNavBar.setVisibility(View.VISIBLE);
- if (result) {
- topicTask = new TopicTask();
- reloadingPage = true;
- topicTask.execute(loadedPageUrl);
- }
+ if (result) {
+ viewModel.reloadPage();
+ } else {
+ Toast.makeText(TopicActivity.this, "Edit failed!", Toast.LENGTH_SHORT).show();
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
index dcf448be..c4d0e71b 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
@@ -2,8 +2,8 @@ package gr.thmmy.mthmmy.activities.topic;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
+import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
@@ -17,9 +17,7 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.RecyclerView;
-import android.text.Editable;
import android.text.TextUtils;
-import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,7 +35,6 @@ import android.widget.TextView;
import com.squareup.picasso.Picasso;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -49,6 +46,7 @@ import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CircleTransform;
+import gr.thmmy.mthmmy.viewmodel.TopicViewModel;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -71,119 +69,72 @@ class TopicAdapter extends RecyclerView.Adapter {
*/
private static int THUMBNAIL_SIZE;
private final Context context;
- private String topicTitle;
- private String baseUrl;
- private final ArrayList toQuoteList = new ArrayList<>();
+ private final OnPostFocusChangeListener postFocusListener;
private final List postsList;
- /**
- * Used to hold the state of visibility and other attributes for views that are animated or
- * otherwise changed. Used in combination with {@link #isUserExtraInfoVisibile} and
- * {@link #isQuoteButtonChecked}.
- */
- private final ArrayList viewProperties = new ArrayList<>();
- /**
- * Index of state indicator in the boolean array. If true user's extra info are expanded and
- * visible.
- */
- private static final int isUserExtraInfoVisibile = 0;
- /**
- * Index of state indicator in the boolean array. If true quote button for this post is checked.
- */
- private static final int isQuoteButtonChecked = 1;
- private TopicActivity.TopicTask topicTask;
- private TopicActivity.ReplyTask replyTask;
- private TopicActivity.DeleteTask deleteTask;
- private final int VIEW_TYPE_POST = 0;
- private final int VIEW_TYPE_QUICK_REPLY = 1;
-
- private final String[] replyDataHolder = new String[2];
- private final int replySubject = 0, replyText = 1;
- private String numReplies, seqnum, sc, topic, buildedQuotes;
- private boolean canReply = false;
+ private TopicViewModel viewModel;
/**
* @param context the context of the {@link RecyclerView}
* @param postsList List of {@link Post} objects to use
*/
- TopicAdapter(Context context, List postsList, String baseUrl,
- TopicActivity.TopicTask topicTask) {
+ TopicAdapter(TopicActivity context, List postsList) {
this.context = context;
this.postsList = postsList;
- this.baseUrl = baseUrl;
-
- THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size);
- for (int i = 0; i < postsList.size(); ++i) {
- //Initializes properties, array's values will be false by default
- viewProperties.add(new boolean[3]);
- }
- this.topicTask = topicTask;
- }
-
- ArrayList getToQuoteList() {
- return toQuoteList;
- }
+ this.postFocusListener = context;
- void prepareForReply(TopicActivity.ReplyTask replyTask, String topicTitle, String numReplies,
- String seqnum, String sc, String topic, String buildedQuotes) {
- this.replyTask = replyTask;
- this.topicTitle = topicTitle;
- this.numReplies = numReplies;
- this.seqnum = seqnum;
- this.sc = sc;
- this.topic = topic;
- this.buildedQuotes = buildedQuotes;
- }
+ viewModel = ViewModelProviders.of(context).get(TopicViewModel.class);
- void prepareForDelete(TopicActivity.DeleteTask deleteTask) {
- this.deleteTask = deleteTask;
+ THUMBNAIL_SIZE = (int) context.getResources().getDimension(R.dimen.thumbnail_size);
}
@Override
public int getItemViewType(int position) {
- return postsList.get(position) == null ? VIEW_TYPE_QUICK_REPLY : VIEW_TYPE_POST;
+ return postsList.get(position).getPostType();
}
+ @NonNull
@Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- if (viewType == VIEW_TYPE_POST) {
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ if (viewType == Post.TYPE_POST) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.activity_topic_post_row, parent, false);
return new PostViewHolder(itemView);
- } else if (viewType == VIEW_TYPE_QUICK_REPLY) {
+ } else if (viewType == Post.TYPE_QUICK_REPLY) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.activity_topic_quick_reply_row, parent, false);
view.findViewById(R.id.quick_reply_submit).setEnabled(true);
final EditText quickReplyText = view.findViewById(R.id.quick_reply_text);
quickReplyText.setFocusableInTouchMode(true);
- quickReplyText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- quickReplyText.post(new Runnable() {
- @Override
- public void run() {
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(quickReplyText, InputMethodManager.SHOW_IMPLICIT);
- }
- });
- }
- });
+ quickReplyText.setOnFocusChangeListener((v, hasFocus) -> quickReplyText.post(() -> {
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(quickReplyText, InputMethodManager.SHOW_IMPLICIT);
+ }));
quickReplyText.requestFocus();
- //Default post subject
- replyDataHolder[replySubject] = "Re: " + topicTitle;
- //Build quotes
- if (!Objects.equals(buildedQuotes, ""))
- replyDataHolder[replyText] = buildedQuotes;
- return new QuickReplyViewHolder(view, new CustomEditTextListener(replySubject),
- new CustomEditTextListener(replyText));
+ return new QuickReplyViewHolder(view);
+ } else if (viewType == Post.TYPE_EDIT) {
+ View view = LayoutInflater.from(parent.getContext()).
+ inflate(R.layout.activity_topic_edit_row, parent, false);
+ view.findViewById(R.id.edit_message_submit).setEnabled(true);
+
+ final EditText editPostEdittext = view.findViewById(R.id.edit_message_text);
+ editPostEdittext.setFocusableInTouchMode(true);
+ editPostEdittext.setOnFocusChangeListener((v, hasFocus) -> editPostEdittext.post(() -> {
+ InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(editPostEdittext, InputMethodManager.SHOW_IMPLICIT);
+ }));
+ editPostEdittext.requestFocus();
+
+ return new EditMessageViewHolder(view);
+ } else {
+ throw new IllegalArgumentException("Unknown view type");
}
- return null;
}
@SuppressLint({"SetJavaScriptEnabled", "SetTextI18n"})
@Override
- public void onBindViewHolder(final RecyclerView.ViewHolder currentHolder,
+ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder currentHolder,
final int position) {
if (currentHolder instanceof PostViewHolder) {
final Post currentPost = postsList.get(position);
@@ -245,12 +196,7 @@ class TopicAdapter extends RecyclerView.Adapter {
attached.setTextColor(filesTextColor);
attached.setPadding(0, 3, 0, 3);
- attached.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- ((BaseActivity) context).downloadFile(attachedFile);
- }
- });
+ attached.setOnClickListener(view -> ((BaseActivity) context).downloadFile(attachedFile));
holder.postFooter.addView(attached);
}
@@ -329,11 +275,11 @@ class TopicAdapter extends RecyclerView.Adapter {
, "fonts/fontawesome-webfont.ttf"));
String aStar = context.getResources().getString(R.string.fa_icon_star);
- String usersStars = "";
+ StringBuilder usersStars = new StringBuilder();
for (int i = 0; i < mNumberOfStars; ++i) {
- usersStars += aStar;
+ usersStars.append(aStar);
}
- holder.stars.setText(usersStars);
+ holder.stars.setText(usersStars.toString());
holder.stars.setTextColor(mUserColor);
holder.stars.setVisibility(View.VISIBLE);
} else
@@ -349,7 +295,7 @@ class TopicAdapter extends RecyclerView.Adapter {
} else holder.cardChildLinear.setBackground(null);
//Avoid's view's visibility recycling
- if (!currentPost.isDeleted() && viewProperties.get(position)[isUserExtraInfoVisibile]) {
+ if (!currentPost.isDeleted() && viewModel.isUserExtraInfoVisible(holder.getAdapterPosition())) {
holder.userExtraInfo.setVisibility(View.VISIBLE);
holder.userExtraInfo.setAlpha(1.0f);
@@ -372,53 +318,39 @@ class TopicAdapter extends RecyclerView.Adapter {
}
if (!currentPost.isDeleted()) {
//Sets graphics behavior
- holder.thumbnail.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- //Clicking the thumbnail opens user's profile
- Intent intent = new Intent(context, ProfileActivity.class);
- Bundle extras = new Bundle();
- extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
- if (currentPost.getThumbnailURL() == null)
- extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
- else
- extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailURL());
- extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
- intent.putExtras(extras);
- intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- }
+ holder.thumbnail.setOnClickListener(view -> {
+ //Clicking the thumbnail opens user's profile
+ Intent intent = new Intent(context, ProfileActivity.class);
+ Bundle extras = new Bundle();
+ extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
+ if (currentPost.getThumbnailURL() == null)
+ extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
+ else
+ extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailURL());
+ extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
+ intent.putExtras(extras);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
});
- holder.header.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //Clicking the header makes it expand/collapse
- boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
- tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile];
- viewProperties.set(holder.getAdapterPosition(), tmp);
- TopicAnimations.animateUserExtraInfoVisibility(holder.username,
- holder.subject, Color.parseColor("#FFFFFF"),
- Color.parseColor("#757575"), holder.userExtraInfo);
- }
+ holder.header.setOnClickListener(v -> {
+ //Clicking the header makes it expand/collapse
+ viewModel.toggleUserInfo(holder.getAdapterPosition());
+ TopicAnimations.animateUserExtraInfoVisibility(holder.username,
+ holder.subject, Color.parseColor("#FFFFFF"),
+ Color.parseColor("#757575"), holder.userExtraInfo);
});
//Clicking the expanded part of a header (the extra info) makes it collapse
- holder.userExtraInfo.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
- tmp[isUserExtraInfoVisibile] = false;
- viewProperties.set(holder.getAdapterPosition(), tmp);
-
- TopicAnimations.animateUserExtraInfoVisibility(holder.username,
- holder.subject, Color.parseColor("#FFFFFF"),
- Color.parseColor("#757575"), (LinearLayout) v);
- }
+ holder.userExtraInfo.setOnClickListener(v -> {
+ viewModel.hideUserInfo(holder.getAdapterPosition());
+ TopicAnimations.animateUserExtraInfoVisibility(holder.username,
+ holder.subject, Color.parseColor("#FFFFFF"),
+ Color.parseColor("#757575"), (LinearLayout) v);
});
} else {
holder.header.setOnClickListener(null);
holder.userExtraInfo.setOnClickListener(null);
}
-
+
holder.overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -449,8 +381,20 @@ class TopicAdapter extends RecyclerView.Adapter {
popUp.dismiss();
}
});
+ }
+
+ final TextView editPostButton = popUpContent.findViewById(R.id.edit_post);
- TextView deletePostButton = popUpContent.findViewById(R.id.delete_post);
+ if (viewModel.isEditingPost() || currentPost.getPostEditURL() == null || currentPost.getPostEditURL().equals("")) {
+ editPostButton.setVisibility(View.GONE);
+ } else {
+ editPostButton.setOnClickListener(v -> {
+ viewModel.prepareForEdit(position, postsList.get(position).getPostEditURL());
+ popUp.dismiss();
+ });
+ }
+
+ TextView deletePostButton = popUpContent.findViewById(R.id.delete_post);
if (currentPost.getPostDeleteURL() == null || currentPost.getPostDeleteURL().equals("")) {
deletePostButton.setVisibility(View.GONE);
@@ -477,37 +421,25 @@ class TopicAdapter extends RecyclerView.Adapter {
});
}
- //Displays the popup
- popUp.showAsDropDown(holder.overflowButton);
- }
+ //Displays the popup
+ popUp.showAsDropDown(holder.overflowButton);
});
//noinspection PointlessBooleanExpression,ConstantConditions
- if (!BaseActivity.getSessionManager().isLoggedIn() || !canReply) {
+ if (!BaseActivity.getSessionManager().isLoggedIn() || !viewModel.canReply()) {
holder.quoteToggle.setVisibility(View.GONE);
} else {
- if (viewProperties.get(position)[isQuoteButtonChecked])
+ if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
else
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_grey_24dp);
//Sets graphics behavior
- holder.quoteToggle.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
- if (tmp[isQuoteButtonChecked]) {
- if (toQuoteList.contains(postsList.indexOf(currentPost))) {
- toQuoteList.remove(toQuoteList.indexOf(postsList.indexOf(currentPost)));
- } else
- Timber.i("An error occurred while trying to exclude post fromtoQuoteList, post wasn't there!");
- holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_grey_24dp);
- } else {
- toQuoteList.add(postsList.indexOf(currentPost));
- holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
- }
- tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked];
- viewProperties.set(holder.getAdapterPosition(), tmp);
- }
+ holder.quoteToggle.setOnClickListener(view -> {
+ viewModel.postIndexToggle(currentPost.getPostIndex());
+ if (viewModel.getToQuoteList().contains(currentPost.getPostIndex()))
+ holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked_accent_24dp);
+ else
+ holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked_grey_24dp);
});
}
} else if (currentHolder instanceof QuickReplyViewHolder) {
@@ -525,41 +457,65 @@ class TopicAdapter extends RecyclerView.Adapter {
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
- holder.quickReplySubject.setText(replyDataHolder[replySubject]);
+ holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle());
- if (replyDataHolder[replyText] != null && !Objects.equals(replyDataHolder[replyText], ""))
- holder.quickReply.setText(replyDataHolder[replyText]);
+ holder.quickReply.setText(viewModel.getBuildedQuotes());
- holder.submitButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (holder.quickReplySubject.getText().toString().isEmpty()) return;
- if (holder.quickReply.getText().toString().isEmpty()) return;
- holder.submitButton.setEnabled(false);
- replyTask.execute(holder.quickReplySubject.getText().toString(),
- holder.quickReply.getText().toString(), numReplies, seqnum, sc, topic);
-
- holder.quickReplySubject.getText().clear();
- holder.quickReplySubject.setText("Re: " + topicTitle);
- holder.quickReply.getText().clear();
- holder.submitButton.setEnabled(true);
- }
+
+ holder.submitButton.setOnClickListener(view -> {
+ if (holder.quickReplySubject.getText().toString().isEmpty()) return;
+ if (holder.quickReply.getText().toString().isEmpty()) return;
+ holder.submitButton.setEnabled(false);
+
+ viewModel.postReply(context, holder.quickReplySubject.getText().toString(),
+ holder.quickReply.getText().toString());
+
+ holder.quickReplySubject.getText().clear();
+ holder.quickReplySubject.setText("Re: " + viewModel.getTopicTitle());
+ holder.quickReply.getText().clear();
+ holder.submitButton.setEnabled(true);
});
+
+
if (backPressHidden) {
holder.quickReply.requestFocus();
backPressHidden = false;
}
- }
- }
+ } else if (currentHolder instanceof EditMessageViewHolder) {
+ final EditMessageViewHolder holder = (EditMessageViewHolder) currentHolder;
+
+ //noinspection ConstantConditions
+ Picasso.with(context)
+ .load(getSessionManager().getAvatarLink())
+ .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
+ .centerCrop()
+ .error(ResourcesCompat.getDrawable(context.getResources()
+ , R.drawable.ic_default_user_thumbnail_white_24dp, null))
+ .placeholder(ResourcesCompat.getDrawable(context.getResources()
+ , R.drawable.ic_default_user_thumbnail_white_24dp, null))
+ .transform(new CircleTransform())
+ .into(holder.thumbnail);
+ holder.username.setText(getSessionManager().getUsername());
+
+ holder.editSubject.setText(postsList.get(position).getSubject());
+ holder.editMessage.setText(viewModel.getPostBeingEditedText());
+
+ holder.submitButton.setOnClickListener(view -> {
+ if (holder.editSubject.getText().toString().isEmpty()) return;
+ if (holder.editMessage.getText().toString().isEmpty()) return;
+ holder.submitButton.setEnabled(false);
+
+ 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);
+ });
- void resetTopic(String baseUrl, TopicActivity.TopicTask topicTask, boolean canReply) {
- this.baseUrl = baseUrl;
- this.topicTask = topicTask;
- this.canReply = canReply;
- viewProperties.clear();
- for (int i = 0; i < postsList.size(); ++i) {
- //Initializes properties, array's values will be false by default
- viewProperties.add(new boolean[3]);
+ if (backPressHidden) {
+ holder.editMessage.requestFocus();
+ backPressHidden = false;
+ }
}
}
@@ -629,19 +585,33 @@ class TopicAdapter extends RecyclerView.Adapter {
final EditText quickReply, quickReplySubject;
final AppCompatImageButton submitButton;
- QuickReplyViewHolder(View quickReply, CustomEditTextListener replySubject
- , CustomEditTextListener replyText) {
+ QuickReplyViewHolder(View quickReply) {
super(quickReply);
thumbnail = quickReply.findViewById(R.id.thumbnail);
username = quickReply.findViewById(R.id.username);
this.quickReply = quickReply.findViewById(R.id.quick_reply_text);
- this.quickReply.addTextChangedListener(replyText);
quickReplySubject = quickReply.findViewById(R.id.quick_reply_subject);
- quickReplySubject.addTextChangedListener(replySubject);
submitButton = quickReply.findViewById(R.id.quick_reply_submit);
}
}
+ private static class EditMessageViewHolder extends RecyclerView.ViewHolder {
+ final ImageView thumbnail;
+ final TextView username;
+ final EditText editMessage, editSubject;
+ final AppCompatImageButton submitButton;
+
+ EditMessageViewHolder(View editView) {
+ super(editView);
+
+ thumbnail = editView.findViewById(R.id.thumbnail);
+ username = editView.findViewById(R.id.username);
+ editMessage = editView.findViewById(R.id.edit_message_text);
+ editSubject = editView.findViewById(R.id.edit_message_subject);
+ submitButton = editView.findViewById(R.id.edit_message_submit);
+ }
+ }
+
/**
* This class is used to handle link clicks in WebViews. When link url is one that the app can
* handle internally, it does. Otherwise user is prompt to open the link in a browser.
@@ -667,26 +637,41 @@ class TopicAdapter extends RecyclerView.Adapter {
final String uriString = uri.toString();
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(uri);
+ viewModel.stopLoading();
if (target.is(ThmmyPage.PageCategory.TOPIC)) {
//This url points to a topic
- //Checks if this is the current topic
- if (Objects.equals(uriString.substring(0, uriString.lastIndexOf(".")), baseUrl)) {
- //Gets uri's targeted message's index number
- String msgIndexReq = uriString.substring(uriString.indexOf("msg") + 3);
- if (msgIndexReq.contains("#"))
- msgIndexReq = msgIndexReq.substring(0, msgIndexReq.indexOf("#"));
- else
- msgIndexReq = msgIndexReq.substring(0, msgIndexReq.indexOf(";"));
-
- //Checks if this post is in the current topic's page
- for (Post post : postsList) {
- if (post.getPostIndex() == Integer.parseInt(msgIndexReq)) {
- // TODO Don't restart Activity, Just change post focus
+ //Checks if the page to be loaded is the one already shown
+ if (uriString.contains(viewModel.getBaseUrl())) {
+ Timber.e("reached here!");
+ if (uriString.contains("topicseen#new") || uriString.contains("#new")) {
+ if (viewModel.getCurrentPageIndex() == viewModel.getPageCount()) {
+ //same page
+ postFocusListener.onPostFocusChange(getItemCount() - 1);
+ Timber.e("new");
return true;
}
}
-
- topicTask.execute(uri.toString());
+ if (uriString.contains("msg")) {
+ String tmpUrlSbstr = uriString.substring(uriString.indexOf("msg") + 3);
+ if (tmpUrlSbstr.contains("msg"))
+ tmpUrlSbstr = tmpUrlSbstr.substring(0, tmpUrlSbstr.indexOf("msg") - 1);
+ int testAgainst = Integer.parseInt(tmpUrlSbstr);
+ Timber.e("reached tthere! %s", testAgainst);
+ for (int i = 0; i < postsList.size(); i++) {
+ if (postsList.get(i).getPostIndex() == testAgainst) {
+ //same page
+ Timber.e(Integer.toString(i));
+ postFocusListener.onPostFocusChange(i);
+ return true;
+ }
+ }
+ } else if ((Objects.equals(uriString, viewModel.getBaseUrl()) && viewModel.getCurrentPageIndex() == 1) ||
+ Integer.parseInt(uriString.substring(viewModel.getBaseUrl().length() + 1)) / 15 + 1 ==
+ viewModel.getCurrentPageIndex()) {
+ //same page
+ Timber.e("ha");
+ return true;
+ }
}
Intent intent = new Intent(context, TopicActivity.class);
@@ -727,25 +712,9 @@ class TopicAdapter extends RecyclerView.Adapter {
}
- private class CustomEditTextListener implements TextWatcher {
- private final int positionInDataHolder;
-
- CustomEditTextListener(int positionInDataHolder) {
- this.positionInDataHolder = positionInDataHolder;
- }
-
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- replyDataHolder[positionInDataHolder] = charSequence.toString();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
+ //we need to set a callback to topic activity to scroll the recyclerview when post focus is requested
+ public interface OnPostFocusChangeListener {
+ void onPostFocusChange(int position);
}
/**
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
index 3ead462c..dd354bf7 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
@@ -28,7 +28,7 @@ import timber.log.Timber;
* {@link #parseTopicNumberOfPages(Document, int, ParseHelpers.Language)}
* {@link #parseTopic(Document, ParseHelpers.Language)}
*/
-class TopicParser {
+public class TopicParser {
//User colors
private static final int USER_COLOR_BLACK = Color.parseColor("#000000");
private static final int USER_COLOR_RED = Color.parseColor("#F44336");
@@ -48,7 +48,7 @@ class TopicParser {
* @return String containing html with the usernames of users
* @see org.jsoup.Jsoup Jsoup
*/
- static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) {
+ public static String parseUsersViewingThisTopic(Document topic, ParseHelpers.Language language) {
if (language.is(ParseHelpers.Language.GREEK))
return topic.select("td:containsOwn(διαβάζουν αυτό το θέμα)").first().html();
return topic.select("td:containsOwn(are viewing this topic)").first().html();
@@ -64,7 +64,7 @@ class TopicParser {
* @return int containing parsed topic's current page
* @see org.jsoup.Jsoup Jsoup
*/
- static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
+ public static int parseCurrentPageIndex(Document topic, ParseHelpers.Language language) {
int parsedPage = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
@@ -102,7 +102,7 @@ class TopicParser {
* @return int containing the number of pages
* @see org.jsoup.Jsoup Jsoup
*/
- static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
+ public static int parseTopicNumberOfPages(Document topic, int currentPage, ParseHelpers.Language language) {
int returnPages = 1;
if (language.is(ParseHelpers.Language.GREEK)) {
@@ -140,7 +140,7 @@ class TopicParser {
* @return {@link ArrayList} of {@link Post}s
* @see org.jsoup.Jsoup Jsoup
*/
- static ArrayList parseTopic(Document topic, ParseHelpers.Language language) {
+ public static ArrayList parseTopic(Document topic, ParseHelpers.Language language) {
//Method's variables
final int NO_INDEX = -1;
ArrayList parsedPostsList = new ArrayList<>();
@@ -157,7 +157,7 @@ class TopicParser {
//Variables for Post constructor
String p_userName, p_thumbnailURL, p_subject, p_post, p_postDate, p_profileURL, p_rank,
p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate,
- p_postURL, p_deletePostURL;
+ p_postURL, p_deletePostURL, p_editPostURL;
int p_postNum, p_postIndex, p_numberOfStars, p_userColor;
boolean p_isDeleted = false;
ArrayList p_attachedFiles;
@@ -174,6 +174,7 @@ class TopicParser {
p_attachedFiles = new ArrayList<>();
p_postLastEditDate = null;
p_deletePostURL = null;
+ p_editPostURL = null;
//Language independent parsing
//Finds thumbnail url
@@ -306,6 +307,12 @@ class TopicParser {
p_deletePostURL = postDelete.attr("href");
}
+ //Finds post modify url
+ Element postEdit = thisRow.select("a:has(img[alt='Modify message'])").first();
+ if (postEdit != null) {
+ p_editPostURL = postEdit.attr("href");
+ }
+
//Finds post's submit date
Element postDate = thisRow.select("div.smalltext:matches(on:)").first();
p_postDate = postDate.text();
@@ -431,13 +438,13 @@ class TopicParser {
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_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
- , p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL));
+ , p_attachedFiles, p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL, Post.TYPE_POST));
} else { //Deleted user
//Add new post in postsList, only standard information needed
parsedPostsList.add(new Post(p_thumbnailURL, p_userName, p_subject, p_post
, p_postIndex , p_postNum, p_postDate, p_userColor, p_attachedFiles
- , p_postLastEditDate, p_postURL, p_deletePostURL));
+ , p_postLastEditDate, p_postURL, p_deletePostURL, p_editPostURL, Post.TYPE_POST));
}
}
return parsedPostsList;
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java
new file mode 100644
index 00000000..5d4a0531
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/DeleteTask.java
@@ -0,0 +1,61 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+
+import java.io.IOException;
+
+import gr.thmmy.mthmmy.activities.topic.Posting;
+import gr.thmmy.mthmmy.base.BaseApplication;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import timber.log.Timber;
+
+public class DeleteTask extends AsyncTask {
+ private DeleteTaskCallbacks listener;
+
+ public DeleteTask(DeleteTaskCallbacks listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ listener.onDeleteTaskStarted();
+ }
+
+ @Override
+ protected Boolean doInBackground(String... args) {
+ Request delete = new Request.Builder()
+ .url(args[0])
+ .header("User-Agent",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
+ .build();
+
+ try {
+ OkHttpClient client = BaseApplication.getInstance().getClient();
+ client.newCall(delete).execute();
+ Response response = client.newCall(delete).execute();
+ //Response response = client.newCall(delete).execute();
+ switch (Posting.replyStatus(response)) {
+ case SUCCESSFUL:
+ return true;
+ default:
+ Timber.e("Something went wrong. Request string: %s", delete.toString());
+ return true;
+ }
+ } catch (IOException e) {
+ Timber.e(e, "Delete failed.");
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ listener.onDeleteTaskFinished(result);
+ }
+
+ public interface DeleteTaskCallbacks {
+ void onDeleteTaskStarted();
+ void onDeleteTaskFinished(boolean result);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java
new file mode 100644
index 00000000..f9fe5444
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/EditTask.java
@@ -0,0 +1,77 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+
+import java.io.IOException;
+
+import gr.thmmy.mthmmy.base.BaseApplication;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import timber.log.Timber;
+
+import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
+
+public class EditTask extends AsyncTask {
+ private EditTaskCallbacks listener;
+ private int position;
+
+ public EditTask(EditTaskCallbacks listener, int position) {
+ this.listener = listener;
+ this.position = position;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ listener.onEditTaskStarted();
+ }
+
+ @Override
+ protected Boolean doInBackground(String... strings) {
+ RequestBody postBody = new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("message", strings[1])
+ .addFormDataPart("num_replies", strings[2])
+ .addFormDataPart("seqnum", strings[3])
+ .addFormDataPart("sc", strings[4])
+ .addFormDataPart("subject", strings[5])
+ .addFormDataPart("topic", strings[6])
+ .build();
+ Request post = new Request.Builder()
+ .url(strings[0])
+ .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
+ .post(postBody)
+ .build();
+
+ try {
+ OkHttpClient client = BaseApplication.getInstance().getClient();
+ client.newCall(post).execute();
+ Response response = client.newCall(post).execute();
+ switch (replyStatus(response)) {
+ case SUCCESSFUL:
+ return true;
+ case NEW_REPLY_WHILE_POSTING:
+ //TODO this...
+ return true;
+ default:
+ Timber.e("Malformed post. Request string: %s", post.toString());
+ return true;
+ }
+ } catch (IOException e) {
+ Timber.e(e, "Edit failed.");
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ listener.onEditTaskFinished(result, position);
+ }
+
+ public interface EditTaskCallbacks {
+ void onEditTaskStarted();
+ void onEditTaskFinished(boolean result, int position);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java
new file mode 100644
index 00000000..a8176072
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditResult.java
@@ -0,0 +1,51 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+public class PrepareForEditResult {
+ private final String postText, commitEditUrl, numReplies, seqnum, sc, topic;
+ private int position;
+ private boolean successful;
+
+ public PrepareForEditResult(String postText, String commitEditUrl, String numReplies, String seqnum,
+ String sc, String topic, int position, boolean successful) {
+ this.postText = postText;
+ this.commitEditUrl = commitEditUrl;
+ this.numReplies = numReplies;
+ this.seqnum = seqnum;
+ this.sc = sc;
+ this.topic = topic;
+ this.position = position;
+ this.successful = successful;
+ }
+
+ public String getPostText() {
+ return postText;
+ }
+
+ public String getCommitEditUrl() {
+ return commitEditUrl;
+ }
+
+ public String getNumReplies() {
+ return numReplies;
+ }
+
+ public String getSeqnum() {
+ return seqnum;
+ }
+
+ public String getSc() {
+ return sc;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public boolean isSuccessful() {
+ return successful;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java
new file mode 100644
index 00000000..7259d580
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForEditTask.java
@@ -0,0 +1,80 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Selector;
+
+import java.io.IOException;
+
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditResult;
+import gr.thmmy.mthmmy.base.BaseApplication;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import timber.log.Timber;
+
+public class PrepareForEditTask extends AsyncTask {
+ private int position;
+ private String replyPageUrl;
+ private PrepareForEditCallbacks listener;
+ private OnPrepareEditFinished finishListener;
+
+ public PrepareForEditTask(PrepareForEditCallbacks listener, OnPrepareEditFinished finishListener, int position, String replyPageUrl) {
+ this.listener = listener;
+ this.finishListener = finishListener;
+ this.position = position;
+ this.replyPageUrl = replyPageUrl;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ listener.onPrepareEditStarted();
+ }
+
+ @Override
+ protected PrepareForEditResult doInBackground(String... strings) {
+ Document document;
+ String url = strings[0];
+ Request request = new Request.Builder()
+ .url(url + ";wap2")
+ .build();
+
+ try {
+ String postText, commitEditURL, numReplies, seqnum, sc, topic;
+ OkHttpClient client = BaseApplication.getInstance().getClient();
+ Response response = client.newCall(request).execute();
+ document = Jsoup.parse(response.body().string());
+
+ Element message = document.select("textarea").first();
+ postText = message.text();
+
+ commitEditURL = document.select("form").first().attr("action");
+ numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
+ seqnum = document.select("input[name=seqnum]").first().attr("value");
+ sc = document.select("input[name=sc]").first().attr("value");
+ topic = document.select("input[name=topic]").first().attr("value");
+
+ return new PrepareForEditResult(postText, commitEditURL, numReplies, seqnum, sc, topic, position, true);
+ } catch (IOException | Selector.SelectorParseException e) {
+ Timber.e(e, "Prepare failed.");
+ return new PrepareForEditResult(null, null, null, null, null, null, position, false);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(PrepareForEditResult result) {
+ finishListener.onPrepareEditFinished(result, position);
+ }
+
+ public interface PrepareForEditCallbacks {
+ void onPrepareEditStarted();
+ void onPrepareEditCancelled();
+ }
+
+ public interface OnPrepareEditFinished {
+ void onPrepareEditFinished(PrepareForEditResult result, int position);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java
new file mode 100644
index 00000000..42ea2774
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReply.java
@@ -0,0 +1,91 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.select.Selector;
+
+import java.io.IOException;
+
+import gr.thmmy.mthmmy.base.BaseApplication;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import timber.log.Timber;
+
+public class PrepareForReply extends AsyncTask {
+ private PrepareForReplyCallbacks listener;
+ private OnPrepareForReplyFinished finishListener;
+ private String replyPageUrl;
+
+ public PrepareForReply(PrepareForReplyCallbacks listener, OnPrepareForReplyFinished finishListener,
+ String replyPageUrl) {
+ this.listener = listener;
+ this.finishListener = finishListener;
+ this.replyPageUrl = replyPageUrl;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ listener.onPrepareForReplyStarted();
+ }
+
+ @Override
+ protected PrepareForReplyResult doInBackground(Integer... postIndices) {
+ String numReplies = null;
+ String seqnum = null;
+ String sc = null;
+ String topic = null;
+ StringBuilder buildedQuotes = new StringBuilder("");
+
+ Document document;
+ Request request = new Request.Builder()
+ .url(replyPageUrl + ";wap2")
+ .build();
+
+ OkHttpClient client = BaseApplication.getInstance().getClient();
+ try {
+ Response response = client.newCall(request).execute();
+ document = Jsoup.parse(response.body().string());
+
+ numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
+ seqnum = document.select("input[name=seqnum]").first().attr("value");
+ sc = document.select("input[name=sc]").first().attr("value");
+ topic = document.select("input[name=topic]").first().attr("value");
+ } catch (IOException | Selector.SelectorParseException e) {
+ Timber.e(e, "Prepare failed.");
+ }
+
+ for (Integer postIndex : postIndices) {
+ request = new Request.Builder()
+ .url("https://www.thmmy.gr/smf/index.php?action=quotefast;quote=" +
+ postIndex + ";" + "sesc=" + sc + ";xml")
+ .build();
+
+ try {
+ Response response = client.newCall(request).execute();
+ String body = response.body().string();
+ buildedQuotes.append(body.substring(body.indexOf("") + 7, body.indexOf("
")));
+ buildedQuotes.append("\n\n");
+ } catch (IOException | Selector.SelectorParseException e) {
+ Timber.e(e, "Quote building failed.");
+ }
+ }
+ return new PrepareForReplyResult(numReplies, seqnum, sc, topic, buildedQuotes.toString());
+ }
+
+ @Override
+ protected void onPostExecute(PrepareForReplyResult result) {
+ finishListener.onPrepareForReplyFinished(result);
+ }
+
+ public interface PrepareForReplyCallbacks {
+ void onPrepareForReplyStarted();
+ void onPrepareForReplyCancelled();
+ }
+
+ public interface OnPrepareForReplyFinished {
+ void onPrepareForReplyFinished(PrepareForReplyResult result);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java
new file mode 100644
index 00000000..b15f77a0
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/PrepareForReplyResult.java
@@ -0,0 +1,34 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+public class PrepareForReplyResult {
+ private final String numReplies, seqnum, sc, topic, buildedQuotes;
+
+
+ public PrepareForReplyResult(String numReplies, String seqnum, String sc, String topic, String buildedQuotes) {
+ this.numReplies = numReplies;
+ this.seqnum = seqnum;
+ this.sc = sc;
+ this.topic = topic;
+ this.buildedQuotes = buildedQuotes;
+ }
+
+ public String getNumReplies() {
+ return numReplies;
+ }
+
+ public String getSeqnum() {
+ return seqnum;
+ }
+
+ public String getSc() {
+ return sc;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public String getBuildedQuotes() {
+ return buildedQuotes;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/ReplyTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/ReplyTask.java
new file mode 100644
index 00000000..69bd0035
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/ReplyTask.java
@@ -0,0 +1,80 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+
+import java.io.IOException;
+
+import gr.thmmy.mthmmy.base.BaseApplication;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import timber.log.Timber;
+
+import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
+
+public class ReplyTask extends AsyncTask {
+ private ReplyTaskCallbacks listener;
+ private boolean includeAppSignature;
+
+ public ReplyTask(ReplyTaskCallbacks listener, boolean includeAppSignature) {
+ this.listener = listener;
+ this.includeAppSignature = includeAppSignature;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ listener.onReplyTaskStarted();
+ }
+
+ @Override
+ protected Boolean doInBackground(String... args) {
+ final String sentFrommTHMMY = includeAppSignature
+ ? "\n[right][size=7pt][i]sent from [url=https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy]mTHMMY [/url][/i][/size][/right]"
+ : "";
+ RequestBody postBody = new MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("message", args[1] + sentFrommTHMMY)
+ .addFormDataPart("num_replies", args[2])
+ .addFormDataPart("seqnum", args[3])
+ .addFormDataPart("sc", args[4])
+ .addFormDataPart("subject", args[0])
+ .addFormDataPart("topic", args[5])
+ .build();
+ Request post = new Request.Builder()
+ .url("https://www.thmmy.gr/smf/index.php?action=post2")
+ .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
+ .post(postBody)
+ .build();
+
+ try {
+ OkHttpClient client = BaseApplication.getInstance().getClient();
+ client.newCall(post).execute();
+ Response response = client.newCall(post).execute();
+ switch (replyStatus(response)) {
+ case SUCCESSFUL:
+ return true;
+ case NEW_REPLY_WHILE_POSTING:
+ //TODO this...
+ return true;
+ default:
+ Timber.e("Malformed post. Request string: %s", post.toString());
+ return true;
+ }
+ } catch (IOException e) {
+ Timber.e(e, "Post failed.");
+ return false;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ listener.onReplyTaskFinished(result);
+ }
+
+ public interface ReplyTaskCallbacks {
+ void onReplyTaskStarted();
+ void onReplyTaskFinished(boolean result);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
new file mode 100644
index 00000000..e233ae1c
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTask.java
@@ -0,0 +1,172 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.os.AsyncTask;
+import android.util.SparseArray;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Objects;
+
+import gr.thmmy.mthmmy.activities.topic.TopicParser;
+import gr.thmmy.mthmmy.base.BaseApplication;
+import gr.thmmy.mthmmy.model.Post;
+import gr.thmmy.mthmmy.model.ThmmyPage;
+import gr.thmmy.mthmmy.utils.parsing.ParseException;
+import gr.thmmy.mthmmy.utils.parsing.ParseHelpers;
+import okhttp3.Request;
+import okhttp3.Response;
+import timber.log.Timber;
+
+/**
+ * An {@link AsyncTask} that handles asynchronous fetching of this topic page and parsing of its
+ * data.
+ * TopicTask's {@link AsyncTask#execute execute} method needs a topic's url as String
+ * parameter.
+ */
+public class TopicTask extends AsyncTask {
+ private TopicTaskObserver topicTaskObserver;
+ private OnTopicTaskCompleted finishListener;
+
+ public TopicTask(TopicTaskObserver topicTaskObserver, OnTopicTaskCompleted finishListener) {
+ this.topicTaskObserver = topicTaskObserver;
+ this.finishListener = finishListener;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ topicTaskObserver.onTopicTaskStarted();
+ }
+
+ @Override
+ protected TopicTaskResult doInBackground(String... strings) {
+ String topicTitle = null;
+ String topicTreeAndMods = "";
+ String topicViewers = "";
+ ArrayList newPostsList = null;
+ int loadedPageTopicId = -1;
+ int focusedPostIndex = 0;
+ SparseArray pagesUrls = new SparseArray<>();
+ int currentPageIndex = 1;
+ int pageCount = 1;
+ String baseUrl = "";
+ String lastPageLoadAttemptedUrl = "";
+
+ Document topic = null;
+ String newPageUrl = strings[0];
+
+ //Finds the index of message focus if present
+ int postFocus = 0;
+ {
+ if (newPageUrl.contains("msg")) {
+ String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
+ if (tmp.contains(";"))
+ postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";")));
+ else if (tmp.contains("#"))
+ postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#")));
+ }
+ }
+
+ 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()
+ .url(newPageUrl)
+ .build();
+ ResultCode resultCode;
+ try {
+ Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
+ topic = Jsoup.parse(response.body().string());
+
+ ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
+
+ //Finds topic's tree, mods and users viewing
+ topicTreeAndMods = topic.select("div.nav").first().html();
+ topicViewers = TopicParser.parseUsersViewingThisTopic(topic, language);
+
+ //Finds reply page url
+ Element replyButton = topic.select("a:has(img[alt=Reply])").first();
+ if (replyButton == null)
+ replyButton = topic.select("a:has(img[alt=Απάντηση])").first();
+ if (replyButton != null) replyPageUrl = replyButton.attr("href");
+
+ //Finds topic title if missing
+ topicTitle = topic.select("td[id=top_subject]").first().text();
+ if (topicTitle.contains("Topic:")) {
+ topicTitle = topicTitle.substring(topicTitle.indexOf("Topic:") + 7
+ , topicTitle.indexOf("(Read") - 2);
+ } else {
+ topicTitle = topicTitle.substring(topicTitle.indexOf("Θέμα:") + 6
+ , topicTitle.indexOf("(Αναγνώστηκε") - 2);
+ Timber.d("Parsed title: %s", topicTitle);
+ }
+
+ //Finds current page's index
+ currentPageIndex = TopicParser.parseCurrentPageIndex(topic, language);
+
+ //Finds number of pages
+ pageCount = TopicParser.parseTopicNumberOfPages(topic, currentPageIndex, language);
+
+ for (int i = 0; i < pageCount; i++) {
+ //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));
+
+ //Finds the position of the focused message if present
+ for (int i = 0; i < newPostsList.size(); ++i) {
+ if (newPostsList.get(i).getPostIndex() == postFocus) {
+ focusedPostIndex = i;
+ break;
+ }
+ }
+ resultCode = ResultCode.SUCCESS;
+ } catch (IOException e) {
+ Timber.i(e, "IO Exception");
+ resultCode = ResultCode.NETWORK_ERROR;
+ } catch (Exception e) {
+ if (isUnauthorized(topic)) {
+ resultCode = ResultCode.UNAUTHORIZED;
+ } else {
+ Timber.e(e, "Parsing Error");
+ resultCode = ResultCode.PARSING_ERROR;
+ }
+ }
+ return new TopicTaskResult(resultCode, baseUrl, topicTitle, replyPageUrl, newPostsList,
+ loadedPageTopicId, currentPageIndex, pageCount, focusedPostIndex, topicTreeAndMods,
+ topicViewers, lastPageLoadAttemptedUrl, pagesUrls);
+ }
+
+ private boolean isUnauthorized(Document document) {
+ return document != null && document.select("body:contains(The topic or board you" +
+ " are looking for appears to be either missing or off limits to you.)," +
+ "body:contains(Το θέμα ή πίνακας που ψάχνετε ή δεν υπάρχει ή δεν " +
+ "είναι προσβάσιμο από εσάς.)").size() > 0;
+ }
+
+ @Override
+ protected void onPostExecute(TopicTaskResult topicTaskResult) {
+ finishListener.onTopicTaskCompleted(topicTaskResult);
+ }
+
+ public enum ResultCode {
+ SUCCESS, NETWORK_ERROR, PARSING_ERROR, UNAUTHORIZED
+ }
+
+ public interface TopicTaskObserver {
+ void onTopicTaskStarted();
+
+ void onTopicTaskCancelled();
+ }
+
+ public interface OnTopicTaskCompleted {
+ void onTopicTaskCompleted(TopicTaskResult result);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
new file mode 100644
index 00000000..f9b76736
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/tasks/TopicTaskResult.java
@@ -0,0 +1,125 @@
+package gr.thmmy.mthmmy.activities.topic.tasks;
+
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+
+import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask;
+import gr.thmmy.mthmmy.model.Post;
+
+public class TopicTaskResult {
+ 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
+ * bundle and is rendered in the toolbar while parsing this topic. Later, if a different topic
+ * title is parsed from the html source, it gets updated.
+ */
+ private final String topicTitle;
+ /**
+ * This topic's reply url
+ */
+ private final String replyPageUrl;
+ private final ArrayList newPostsList;
+ /**
+ * The topicId of the loaded page
+ */
+ private final int loadedPageTopicId;
+ /**
+ * Holds current page's index (starting from 1, not 0)
+ */
+ private final int currentPageIndex;
+ /**
+ * Holds the requested topic's number of pages
+ */
+ private final int pageCount;
+ /**
+ * The index of the post that has focus
+ */
+ private final int focusedPostIndex;
+ //Topic's info related
+ private final String topicTreeAndMods;
+ private final String topicViewers;
+ /**
+ * The url of the last page that was attempted to be loaded
+ */
+ private final String lastPageLoadAttemptedUrl;
+ private final SparseArray pagesUrls;
+
+ public TopicTaskResult(TopicTask.ResultCode resultCode, String baseUrl, String topicTitle,
+ String replyPageUrl, ArrayList newPostsList, int loadedPageTopicId,
+ int currentPageIndex, int pageCount, int focusedPostIndex, String topicTreeAndMods,
+ String topicViewers, String lastPageLoadAttemptedUrl, SparseArray pagesUrls) {
+ this.resultCode = resultCode;
+ this.baseUrl = baseUrl;
+ this.topicTitle = topicTitle;
+ this.replyPageUrl = replyPageUrl;
+ this.newPostsList = newPostsList;
+ this.loadedPageTopicId = loadedPageTopicId;
+ this.currentPageIndex = currentPageIndex;
+ this.pageCount = pageCount;
+ this.focusedPostIndex = focusedPostIndex;
+ this.topicTreeAndMods = topicTreeAndMods;
+ this.topicViewers = topicViewers;
+ this.lastPageLoadAttemptedUrl = lastPageLoadAttemptedUrl;
+ this.pagesUrls = pagesUrls;
+ }
+
+ public TopicTask.ResultCode getResultCode() {
+ return resultCode;
+ }
+
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ public String getTopicTitle() {
+ return topicTitle;
+ }
+
+ public String getReplyPageUrl() {
+ return replyPageUrl;
+ }
+
+ public ArrayList getNewPostsList() {
+ return newPostsList;
+ }
+
+ public int getLoadedPageTopicId() {
+ return loadedPageTopicId;
+ }
+
+ public int getCurrentPageIndex() {
+ return currentPageIndex;
+ }
+
+ public int getPageCount() {
+ return pageCount;
+ }
+
+ public int getFocusedPostIndex() {
+ return focusedPostIndex;
+ }
+
+ public String getTopicTreeAndMods() {
+ return topicTreeAndMods;
+ }
+
+ public String getTopicViewers() {
+ return topicViewers;
+ }
+
+ public String getLastPageLoadAttemptedUrl() {
+ return lastPageLoadAttemptedUrl;
+ }
+
+ public SparseArray getPagesUrls() {
+ return pagesUrls;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
index 32f3e891..94521fc9 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
@@ -2,6 +2,7 @@ package gr.thmmy.mthmmy.base;
import android.Manifest;
import android.app.ProgressDialog;
+import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -55,6 +56,7 @@ import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.DownloadHelper;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.FileUtils;
+import gr.thmmy.mthmmy.viewmodel.BaseViewModel;
import okhttp3.OkHttpClient;
import timber.log.Timber;
@@ -107,6 +109,10 @@ public abstract class BaseActivity extends AppCompatActivity {
loadSavedBookmarks();
}
+ BaseViewModel baseViewModel = ViewModelProviders.of(this).get(BaseViewModel.class);
+ baseViewModel.getCurrentPageBookmark().observe(this, thisPageBookmark -> {
+ setTopicBookmark(thisPageBookmarkMenuButton);
+ });
}
@Override
diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java
index 3206061d..8641f288 100644
--- a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java
+++ b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java
@@ -16,6 +16,10 @@ import java.util.Objects;
* previous fields.
*/
public class Post {
+ public static final int TYPE_POST = 0;
+ public static final int TYPE_QUICK_REPLY = 1;
+ public static final int TYPE_EDIT = 2;
+
//Standard info (exists in every post)
private final String thumbnailUrl;
private final String author;
@@ -30,6 +34,8 @@ public class Post {
private final String lastEdit;
private final String postURL;
private final String postDeleteURL;
+ private final String postEditURL;
+ private int postType;
//Extra info
private final String profileURL;
@@ -63,6 +69,8 @@ public class Post {
lastEdit = null;
postURL = null;
postDeleteURL = null;
+ postEditURL = null;
+ postType = -1;
}
/**
@@ -87,14 +95,14 @@ public class Post {
* @param userColor author's user color
* @param attachedFiles post's attached files
* @param lastEdit post's last edit date
- * @param postURL post's URL
+ * @param postURL post's URL
*/
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList attachedFiles, @Nullable String lastEdit, String postURL
- , @Nullable String postDeleteURL) {
+ , @Nullable String postDeleteURL, @Nullable String postEditURL, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@@ -116,6 +124,8 @@ public class Post {
this.numberOfStars = numberOfStars;
this.postURL = postURL;
this.postDeleteURL = postDeleteURL;
+ this.postEditURL = postEditURL;
+ this.postType = postType;
}
/**
@@ -138,7 +148,7 @@ public class Post {
public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList attachedFiles, @Nullable String lastEdit, String postURL
- , @Nullable String postDeleteURL) {
+ , @Nullable String postDeleteURL, @Nullable String postEditURL, int postType) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl;
this.author = author;
@@ -160,6 +170,13 @@ public class Post {
numberOfStars = 0;
this.postURL = postURL;
this.postDeleteURL = postDeleteURL;
+ this.postEditURL = postEditURL;
+ this.postType = postType;
+ }
+
+ public static Post newQuickReply() {
+ return new Post(null, null, null, null, 0, 0, null,
+ 0, null, null, null, null, null, TYPE_QUICK_REPLY);
}
//Getters
@@ -358,4 +375,22 @@ public class Post {
public String getPostDeleteURL() {
return postDeleteURL;
}
+
+ /**
+ * Gets this post's modify url.
+ *
+ * @return post's edit url
+ */
+ @Nullable
+ public String getPostEditURL() {
+ return postEditURL;
+ }
+
+ public int getPostType() {
+ return postType;
+ }
+
+ public void setPostType(int postType) {
+ this.postType = postType;
+ }
}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java b/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
new file mode 100644
index 00000000..2713a2a8
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/utils/HTMLUtils.java
@@ -0,0 +1,76 @@
+package gr.thmmy.mthmmy.utils;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.SpannableStringBuilder;
+import android.text.style.ClickableSpan;
+import android.text.style.URLSpan;
+import android.view.View;
+
+import gr.thmmy.mthmmy.activities.board.BoardActivity;
+import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
+import gr.thmmy.mthmmy.model.ThmmyPage;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
+import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
+import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
+import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
+import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
+
+public class HTMLUtils {
+ private HTMLUtils() {}
+
+ public static SpannableStringBuilder getSpannableFromHtml(Activity activity, String html) {
+ CharSequence sequence;
+ if (Build.VERSION.SDK_INT >= 24) {
+ sequence = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
+ } else {
+ //noinspection deprecation
+ sequence = Html.fromHtml(html);
+ }
+ SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
+ URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
+ for (URLSpan span : urls) {
+ makeLinkClickable(activity, strBuilder, span);
+ }
+ return strBuilder;
+ }
+
+ private static void makeLinkClickable(Activity activity, SpannableStringBuilder strBuilder, final URLSpan span) {
+ int start = strBuilder.getSpanStart(span);
+ int end = strBuilder.getSpanEnd(span);
+ int flags = strBuilder.getSpanFlags(span);
+ ClickableSpan clickable = new ClickableSpan() {
+ @Override
+ public void onClick(View view) {
+ ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
+ if (target.is(ThmmyPage.PageCategory.BOARD)) {
+ Intent intent = new Intent(activity.getApplicationContext(), BoardActivity.class);
+ Bundle extras = new Bundle();
+ extras.putString(BUNDLE_BOARD_URL, span.getURL());
+ extras.putString(BUNDLE_BOARD_TITLE, "");
+ intent.putExtras(extras);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ activity.getApplicationContext().startActivity(intent);
+ } else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
+ Intent intent = new Intent(activity.getApplicationContext(), ProfileActivity.class);
+ Bundle extras = new Bundle();
+ extras.putString(BUNDLE_PROFILE_URL, span.getURL());
+ extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
+ extras.putString(BUNDLE_PROFILE_USERNAME, "");
+ intent.putExtras(extras);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ activity.getApplicationContext().startActivity(intent);
+ } else if (target.is(ThmmyPage.PageCategory.INDEX))
+ activity.finish();
+ }
+ };
+ strBuilder.setSpan(clickable, start, end, flags);
+ strBuilder.removeSpan(span);
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java
new file mode 100644
index 00000000..53ed84ef
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/BaseViewModel.java
@@ -0,0 +1,18 @@
+package gr.thmmy.mthmmy.viewmodel;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.MutableLiveData;
+import android.arch.lifecycle.ViewModel;
+
+import gr.thmmy.mthmmy.model.Bookmark;
+
+public class BaseViewModel extends ViewModel {
+ protected MutableLiveData currentPageBookmark;
+
+ public LiveData getCurrentPageBookmark() {
+ if (currentPageBookmark == null) {
+ currentPageBookmark = new MutableLiveData<>();
+ }
+ return currentPageBookmark;
+ }
+}
diff --git a/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
new file mode 100644
index 00000000..1fb97407
--- /dev/null
+++ b/app/src/main/java/gr/thmmy/mthmmy/viewmodel/TopicViewModel.java
@@ -0,0 +1,316 @@
+package gr.thmmy.mthmmy.viewmodel;
+
+import android.arch.lifecycle.MutableLiveData;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+
+import java.util.ArrayList;
+
+import gr.thmmy.mthmmy.activities.settings.SettingsActivity;
+import gr.thmmy.mthmmy.activities.topic.tasks.DeleteTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.EditTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditResult;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForEditTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReply;
+import gr.thmmy.mthmmy.activities.topic.tasks.PrepareForReplyResult;
+import gr.thmmy.mthmmy.activities.topic.tasks.ReplyTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.TopicTask;
+import gr.thmmy.mthmmy.activities.topic.tasks.TopicTaskResult;
+import gr.thmmy.mthmmy.base.BaseActivity;
+import gr.thmmy.mthmmy.model.Post;
+import gr.thmmy.mthmmy.session.SessionManager;
+
+public class TopicViewModel extends BaseViewModel implements TopicTask.OnTopicTaskCompleted,
+ PrepareForReply.OnPrepareForReplyFinished, PrepareForEditTask.OnPrepareEditFinished {
+ /**
+ * topic state
+ */
+ private boolean editingPost = false;
+ private boolean writingReply = false;
+ /**
+ * A list of {@link Post#getPostIndex()} for building quotes for replying
+ */
+ private ArrayList toQuoteList = new ArrayList<>();
+ /**
+ * caches the expand/collapse state of the user extra info in the current page for the recyclerview
+ */
+ private ArrayList isUserExtraInfoVisibile = new ArrayList<>();
+ /**
+ * holds the adapter position of the post being edited
+ */
+ private int postBeingEditedPosition;
+
+ private TopicTask currentTopicTask;
+ private PrepareForEditTask currentPrepareForEditTask;
+ private PrepareForReply currentPrepareForReplyTask;
+
+ //callbacks for topic activity
+ private TopicTask.TopicTaskObserver topicTaskObserver;
+ private DeleteTask.DeleteTaskCallbacks deleteTaskCallbacks;
+ private ReplyTask.ReplyTaskCallbacks replyFinishListener;
+ private PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks;
+ private EditTask.EditTaskCallbacks editTaskCallbacks;
+ private PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks;
+
+ private MutableLiveData topicTaskResult = new MutableLiveData<>();
+ private MutableLiveData prepareForReplyResult = new MutableLiveData<>();
+ private MutableLiveData 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) {
+ stopLoading();
+ currentTopicTask = new TopicTask(topicTaskObserver, this);
+ currentTopicTask.execute(pageUrl);
+ }
+
+ public void reloadPage() {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("No topic task has finished yet!");
+ loadUrl(topicTaskResult.getValue().getLastPageLoadAttemptedUrl());
+ }
+
+ public void changePage(int pageRequested) {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("No page has been loaded yet!");
+ if (pageRequested != topicTaskResult.getValue().getCurrentPageIndex() - 1)
+ loadUrl(topicTaskResult.getValue().getPagesUrls().get(pageRequested));
+ }
+
+ public void prepareForReply() {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("Topic task has not finished yet!");
+ stopLoading();
+ changePage(topicTaskResult.getValue().getPageCount() - 1);
+ currentPrepareForReplyTask = new PrepareForReply(prepareForReplyCallbacks, this,
+ topicTaskResult.getValue().getReplyPageUrl());
+ currentPrepareForReplyTask.execute(toQuoteList.toArray(new Integer[0]));
+ }
+
+ public void postReply(Context context, String subject, String reply) {
+ if (prepareForReplyResult.getValue() == null) {
+ throw new NullPointerException("Reply preparation was not found!");
+ }
+ PrepareForReplyResult replyForm = prepareForReplyResult.getValue();
+ boolean includeAppSignature = true;
+ SessionManager sessionManager = BaseActivity.getSessionManager();
+ if (sessionManager.isLoggedIn()) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ includeAppSignature = prefs.getBoolean(SettingsActivity.POSTING_APP_SIGNATURE_ENABLE_KEY, true);
+ }
+ toQuoteList.clear();
+ new ReplyTask(replyFinishListener, includeAppSignature).execute(subject, reply,
+ replyForm.getNumReplies(), replyForm.getSeqnum(), replyForm.getSc(), replyForm.getTopic());
+ }
+
+ public void deletePost(String postDeleteUrl) {
+ new DeleteTask(deleteTaskCallbacks).execute(postDeleteUrl);
+ }
+
+ public void prepareForEdit(int position, String postEditURL) {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("Topic task has not finished yet!");
+ stopLoading();
+ currentPrepareForEditTask = new PrepareForEditTask(prepareForEditCallbacks, this, position,
+ topicTaskResult.getValue().getReplyPageUrl());
+ currentPrepareForEditTask.execute(postEditURL);
+ }
+
+ public void editPost(int position, String subject, String message) {
+ if (prepareForEditResult.getValue() == null)
+ throw new NullPointerException("Edit preparation was not found!");
+ PrepareForEditResult editResult = prepareForEditResult.getValue();
+ new EditTask(editTaskCallbacks, position).execute(editResult.getCommitEditUrl(), message,
+ editResult.getNumReplies(), editResult.getSeqnum(), editResult.getSc(), subject, editResult.getTopic());
+ }
+
+ /**
+ * cancel tasks that change the ui
+ * topic, prepare for edit, prepare for reply tasks need to cancel all other ui changing tasks
+ * before starting
+ */
+ public void stopLoading() {
+ if (currentTopicTask != null && currentTopicTask.getStatus() == AsyncTask.Status.RUNNING) {
+ currentTopicTask.cancel(true);
+ topicTaskObserver.onTopicTaskCancelled();
+ }
+ if (currentPrepareForEditTask != null && currentPrepareForEditTask.getStatus() == AsyncTask.Status.RUNNING) {
+ currentPrepareForEditTask.cancel(true);
+ prepareForEditCallbacks.onPrepareEditCancelled();
+ }
+ if (currentPrepareForReplyTask != null && currentPrepareForReplyTask.getStatus() == AsyncTask.Status.RUNNING) {
+ currentPrepareForReplyTask.cancel(true);
+ prepareForReplyCallbacks.onPrepareForReplyCancelled();
+ }
+ // no need to cancel reply, edit and delete task, user should not have to wait for the ui
+ // after he is done posting, editing or deleting
+ }
+
+ // callbacks for viewmodel
+ @Override
+ public void onTopicTaskCompleted(TopicTaskResult result) {
+ topicTaskResult.setValue(result);
+ if (result.getResultCode() == TopicTask.ResultCode.SUCCESS) {
+ isUserExtraInfoVisibile.clear();
+ for (int i = 0; i < result.getNewPostsList().size(); i++) {
+ isUserExtraInfoVisibile.add(false);
+ }
+ }
+ }
+
+ @Override
+ public void onPrepareForReplyFinished(PrepareForReplyResult result) {
+ writingReply = true;
+ prepareForReplyResult.setValue(result);
+ }
+
+ @Override
+ public void onPrepareEditFinished(PrepareForEditResult result, int position) {
+ editingPost = true;
+ postBeingEditedPosition = position;
+ prepareForEditResult.setValue(result);
+ }
+
+ // <-------------Just getters, setters and helper methods below here---------------->
+
+ public boolean isUserExtraInfoVisible(int position) {
+ return isUserExtraInfoVisibile.get(position);
+ }
+
+ public void hideUserInfo(int position) {
+ isUserExtraInfoVisibile.set(position, false);
+ }
+
+ public void toggleUserInfo(int position) {
+ isUserExtraInfoVisibile.set(position, !isUserExtraInfoVisibile.get(position));
+ }
+
+ public ArrayList getToQuoteList() {
+ return toQuoteList;
+ }
+
+ public void postIndexToggle(Integer postIndex) {
+ if (toQuoteList.contains(postIndex))
+ toQuoteList.remove(postIndex);
+ else
+ toQuoteList.add(postIndex);
+ }
+
+ public void setTopicTaskObserver(TopicTask.TopicTaskObserver topicTaskObserver) {
+ this.topicTaskObserver = topicTaskObserver;
+ }
+
+ public void setDeleteTaskCallbacks(DeleteTask.DeleteTaskCallbacks deleteTaskCallbacks) {
+ this.deleteTaskCallbacks = deleteTaskCallbacks;
+ }
+
+ public void setReplyFinishListener(ReplyTask.ReplyTaskCallbacks replyFinishListener) {
+ this.replyFinishListener = replyFinishListener;
+ }
+
+ public void setPrepareForEditCallbacks(PrepareForEditTask.PrepareForEditCallbacks prepareForEditCallbacks) {
+ this.prepareForEditCallbacks = prepareForEditCallbacks;
+ }
+
+ public void setEditTaskCallbacks(EditTask.EditTaskCallbacks editTaskCallbacks) {
+ this.editTaskCallbacks = editTaskCallbacks;
+ }
+
+ public void setPrepareForReplyCallbacks(PrepareForReply.PrepareForReplyCallbacks prepareForReplyCallbacks) {
+ this.prepareForReplyCallbacks = prepareForReplyCallbacks;
+ }
+
+ public MutableLiveData getTopicTaskResult() {
+ return topicTaskResult;
+ }
+
+ public MutableLiveData getPrepareForReplyResult() {
+ return prepareForReplyResult;
+ }
+
+ public MutableLiveData getPrepareForEditResult() {
+ return prepareForEditResult;
+ }
+
+ public void setEditingPost(boolean editingPost) {
+ this.editingPost = editingPost;
+ }
+
+ public boolean isEditingPost() {
+ return editingPost;
+ }
+
+ public int getPostBeingEditedPosition() {
+ return postBeingEditedPosition;
+ }
+
+ public boolean canReply() {
+ return topicTaskResult.getValue() != null && topicTaskResult.getValue().getReplyPageUrl() != null;
+ }
+
+ public boolean isWritingReply() {
+ return writingReply;
+ }
+
+ public void setWritingReply(boolean 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() {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("No page has been loaded yet!");
+ return topicTaskResult.getValue().getCurrentPageIndex();
+ }
+
+ public int getPageCount() {
+ if (topicTaskResult.getValue() == null)
+ throw new NullPointerException("No page has been loaded yet!");
+
+ return topicTaskResult.getValue().getPageCount();
+ }
+
+ public String getPostBeingEditedText() {
+ if (prepareForEditResult.getValue() == null)
+ throw new NullPointerException("Edit preparation was not found!");
+ return prepareForEditResult.getValue().getPostText();
+ }
+
+ public String getBuildedQuotes() {
+ if (prepareForReplyResult.getValue() != null) {
+ return prepareForReplyResult.getValue().getBuildedQuotes();
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_edit_white_24dp.xml b/app/src/main/res/drawable/ic_edit_white_24dp.xml
new file mode 100644
index 00000000..46462b57
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_topic_edit_row.xml b/app/src/main/res/layout/activity_topic_edit_row.xml
new file mode 100644
index 00000000..0e86a581
--- /dev/null
+++ b/app/src/main/res/layout/activity_topic_edit_row.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_topic_overflow_menu.xml b/app/src/main/res/layout/activity_topic_overflow_menu.xml
index 361dac72..95492862 100644
--- a/app/src/main/res/layout/activity_topic_overflow_menu.xml
+++ b/app/src/main/res/layout/activity_topic_overflow_menu.xml
@@ -32,4 +32,19 @@
android:text="@string/post_delete_button"
android:textColor="@color/primary_text" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_topic_quick_reply_row.xml b/app/src/main/res/layout/activity_topic_quick_reply_row.xml
index 7702994c..2554e788 100644
--- a/app/src/main/res/layout/activity_topic_quick_reply_row.xml
+++ b/app/src/main/res/layout/activity_topic_quick_reply_row.xml
@@ -74,7 +74,7 @@
android:layout_height="wrap_content"
android:layout_below="@+id/username"
android:layout_toEndOf="@+id/thumbnail_holder"
- android:hint="@string/quick_reply_subject"
+ android:hint="@string/subject"
android:inputType="textMultiLine"
android:maxLength="80"
android:textSize="10sp"
@@ -110,7 +110,7 @@
android:layout_marginBottom="5dp"
android:layout_marginEnd="5dp"
android:background="@color/card_background"
- android:contentDescription="@string/quick_reply_submit"
+ android:contentDescription="@string/submit"
app:srcCompat="@drawable/ic_send_accent_24dp" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 309f24ce..879671a4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -42,6 +42,7 @@
Overflow menu button
Share
Delete
+ Edit
#%1$d
first
previous
@@ -49,8 +50,9 @@
next
last
Quick reply…
- Subject…
- Submit
+ Subject…
+ Submit
+ Message…
Username