diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/ReplyParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/ReplyParser.java new file mode 100644 index 00000000..4950478b --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/ReplyParser.java @@ -0,0 +1,50 @@ +package gr.thmmy.mthmmy.activities.topic; + +import android.util.Log; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.IOException; + +import okhttp3.Response; + +class ReplyParser { + /** + * Debug Tag for logging debug output to LogCat + */ + @SuppressWarnings("unused") + private static final String TAG = "ReplyParser"; + + enum REPLY_STATUS { + SUCCESSFUL, NO_SUBJECT, EMPTY_BODY, NEW_REPLY_WHILE_POSTING, NOT_FOUND, SESSION_ENDED, OTHER_ERROR + } + + 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(); + if (finalUrl.contains("action=post")) { + Document postErrorPage = Jsoup.parse(response.body().string()); + String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first() + .toString().split("
"); + for (int i = 0; i < errors.length; ++i) { //TODO test + Log.d("TAG", String.valueOf(i)); + Log.d("TAG", errors[i]); + } + for (String error : errors) { + if (error.contains("Your session timed out while posting") || + error.contains("Υπερβήκατε τον μέγιστο χρόνο σύνδεσης κατά την αποστολή")) + return REPLY_STATUS.SESSION_ENDED; + if (error.contains("No subject was filled in") + || error.contains("Δεν δόθηκε τίτλος")) + return REPLY_STATUS.NO_SUBJECT; + if (error.contains("The message body was left empty") + || error.contains("Δεν δόθηκε κείμενο για το μήνυμα")) + return REPLY_STATUS.EMPTY_BODY; + } + return REPLY_STATUS.NEW_REPLY_WHILE_POSTING; + } + return REPLY_STATUS.SUCCESSFUL; + } +} 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 e6860ff0..7f0393a9 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 @@ -40,6 +40,8 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import static gr.thmmy.mthmmy.activities.topic.ReplyParser.replyStatus; + /** * Activity for topics. When creating an Intent of this activity you need to bundle a String * containing this topics's url using the key {@link #BUNDLE_TOPIC_URL} and a String containing @@ -84,6 +86,7 @@ public class TopicActivity extends BaseActivity { private static final int LARGE_STEP = 10; private Integer pageRequestValue; //Bottom navigation graphics + LinearLayout bottomNavBar; private ImageButton firstPage; private ImageButton previousPage; private TextView pageIndicator; @@ -136,12 +139,12 @@ public class TopicActivity extends BaseActivity { recyclerView.setHasFixedSize(true); LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); recyclerView.setLayoutManager(layoutManager); - topicAdapter = new TopicAdapter(this, postsList, topicTask); + topicAdapter = new TopicAdapter(this, postsList, topicTask, topicTitle); recyclerView.setAdapter(topicAdapter); replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); replyFAB.setEnabled(false); - final LinearLayout bottomNavBar = (LinearLayout) findViewById(R.id.bottom_navigation_bar); + bottomNavBar = (LinearLayout) findViewById(R.id.bottom_navigation_bar); if (!sessionManager.isLoggedIn()) replyFAB.hide(); else { replyFAB.setOnClickListener(new View.OnClickListener() { @@ -341,7 +344,7 @@ public class TopicActivity extends BaseActivity { paginationEnabled(true); changePage(pageRequestValue - 1); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { + if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { //TODO fix bug autoIncrement = false; incrementPageRequestValue(thisPage - pageRequestValue); paginationEnabled(true); @@ -437,11 +440,12 @@ public class TopicActivity extends BaseActivity { return SAME_PAGE; } } - } else if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage) + } else if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage) //TODO fix bug return SAME_PAGE; } else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null; loadedPageUrl = newPageUrl; + replyPageUrl = null; Request request = new Request.Builder() .url(newPageUrl) .build(); @@ -477,7 +481,7 @@ public class TopicActivity extends BaseActivity { progressBar.setVisibility(ProgressBar.INVISIBLE); topicAdapter.customNotifyDataSetChanged(new TopicTask()); - if (replyPageUrl == null) replyFAB.setVisibility(View.GONE); + if (replyPageUrl == null) replyFAB.hide(); if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true); //Set current page @@ -521,6 +525,8 @@ public class TopicActivity extends BaseActivity { //Find 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"); } @@ -581,16 +587,14 @@ public class TopicActivity extends BaseActivity { subject = document.select("input[name=subject]").first().attr("value"); topic = document.select("input[name=topic]").first().attr("value"); } catch (IOException e) { - Report.i(TAG, "Post failed.", e); + Report.e(TAG, "Post failed.", e); return false; } catch (Selector.SelectorParseException e) { Report.e(TAG, "Post failed.", e); return false; } - RequestBody postBody = null; - - postBody = new MultipartBody.Builder() + RequestBody postBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("message", message[0]) .addFormDataPart("num_replies", numReplies) @@ -608,10 +612,19 @@ public class TopicActivity extends BaseActivity { try { client.newCall(post).execute(); - client.newCall(post).execute(); - return true; + Response response = client.newCall(post).execute(); + switch (replyStatus(response)) { + case SUCCESSFUL: + return true; + case NEW_REPLY_WHILE_POSTING: + //TODO this... + return true; + default: + Report.e(TAG, "Malformed post. Request string:\n" + post.toString()); + return true; + } } catch (IOException e) { - Report.i(TAG, "Post failed.", e); + Report.e(TAG, "Post failed.", e); return false; } } @@ -619,6 +632,12 @@ public class TopicActivity extends BaseActivity { @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 failed!", Toast.LENGTH_SHORT).show(); + paginationEnabled(true); + replyFAB.setEnabled(true); } } } \ 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 230388be..035933cf 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 @@ -10,12 +10,14 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.content.res.ResourcesCompat; import android.support.v7.widget.AppCompatImageButton; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; +import android.text.Editable; import android.text.TextUtils; -import android.util.Log; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -59,6 +61,7 @@ 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.base.BaseActivity.getSessionManager; /** * Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics. @@ -73,6 +76,7 @@ class TopicAdapter extends RecyclerView.Adapter { */ private static int THUMBNAIL_SIZE; private final Context context; + private String topicTitle; private ArrayList toQuoteList = new ArrayList<>(); private final List postsList; /** @@ -99,13 +103,16 @@ class TopicAdapter extends RecyclerView.Adapter { private TopicActivity.ReplyTask replyTask; private final int VIEW_TYPE_POST = 0; private final int VIEW_TYPE_QUICK_REPLY = 1; - private boolean firstTime = false; + + private String[] replyDataHolder = new String[2]; + private int replySubject = 0, replyText = 1; /** * @param context the context of the {@link RecyclerView} * @param postsList List of {@link Post} objects to use */ - TopicAdapter(Context context, List postsList, TopicActivity.TopicTask topicTask) { + TopicAdapter(Context context, List postsList, TopicActivity.TopicTask topicTask + , String topicTitle) { this.context = context; this.postsList = postsList; @@ -115,11 +122,11 @@ class TopicAdapter extends RecyclerView.Adapter { viewProperties.add(new boolean[3]); } this.topicTask = topicTask; + this.topicTitle = topicTitle; } void prepareForReply(TopicActivity.ReplyTask replyTask) { this.replyTask = replyTask; - firstTime = true; } @Override @@ -136,12 +143,22 @@ class TopicAdapter extends RecyclerView.Adapter { } else if (viewType == VIEW_TYPE_QUICK_REPLY) { View view = LayoutInflater.from(parent.getContext()). inflate(R.layout.activity_topic_quick_reply_row, parent, false); - return new QuickReplyViewHolder(view); + //Default post subject + replyDataHolder[replySubject] = "Re: " + topicTitle; + //Build quotes + String quotes = ""; + for (int quotePosition : toQuoteList) { + quotes += buildQuote(quotePosition); + } + if (!Objects.equals(quotes, "")) + replyDataHolder[replyText] = quotes; + return new QuickReplyViewHolder(view, new CustomEditTextListener(replySubject), + new CustomEditTextListener(replyText)); } return null; } - @SuppressLint("SetJavaScriptEnabled") + @SuppressLint({"SetJavaScriptEnabled", "SetTextI18n"}) @Override public void onBindViewHolder(final RecyclerView.ViewHolder currentHolder, final int position) { if (currentHolder instanceof PostViewHolder) { @@ -407,52 +424,24 @@ class TopicAdapter extends RecyclerView.Adapter { holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView)); } else if (currentHolder instanceof QuickReplyViewHolder) { final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder; - if (firstTime) { - //Build quotes - String quotes = ""; - for (int quotePosition : toQuoteList) { - Date postDate = null; - { - String date = postsList.get(quotePosition).getPostDate(); - if (date != null) { - DateFormat format = new SimpleDateFormat("MMMM d, yyyy, h:m:s a", Locale.ENGLISH); - if (date.contains("Today")) { - date = date.replace("Today at", - Calendar.getInstance().getDisplayName(Calendar.MONTH, - Calendar.LONG, Locale.ENGLISH) - + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); - } else if (date.contains("Σήμερα")) { - date = date.replace("Σήμερα στις", - Calendar.getInstance().getDisplayName(Calendar.MONTH, - Calendar.LONG, Locale.ENGLISH) - + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); - if (date.contains("πμ")) date = date.replace("πμ", "am"); - if (date.contains("μμ")) date = date.replace("μμ", "pm"); - } - - Log.d(TAG, date); - - try { - postDate = format.parse(date); - } catch (ParseException e) { - e.printStackTrace(); - } - } - } - if (postsList.get(quotePosition).getPostIndex() != 0) { - assert postDate != null; - quotes += "[quote author=" + postsList.get(quotePosition).getAuthor() - + " link=topic=68525.msg" + postsList.get(quotePosition).getPostIndex() - + "#msg" + postsList.get(quotePosition).getPostIndex() - + " date=" + postDate.getTime() / 1000 + "]" - + "\n" + postsList.get(quotePosition).getContent() - + "\n" + "[/quote]" + "\n\n"; - } - } - holder.quickReply.setText(quotes); - } + + //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, null)) + .placeholder(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_thumbnail, null)) + .transform(new CircleTransform()) + .into(holder.thumbnail); + holder.username.setText(getSessionManager().getUsername()); + holder.quickReplySubject.setText(replyDataHolder[replySubject]); + + if (replyDataHolder[replyText] != null && !Objects.equals(replyDataHolder[replyText], "")) + holder.quickReply.setText(replyDataHolder[replyText]); + holder.submitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -531,12 +520,23 @@ class TopicAdapter extends RecyclerView.Adapter { * Custom {@link RecyclerView.ViewHolder} implementation */ private static class QuickReplyViewHolder extends RecyclerView.ViewHolder { - final EditText quickReply; + final ImageView thumbnail; + final TextView username; + final EditText quickReply, quickReplySubject; final AppCompatImageButton submitButton; + final CustomEditTextListener replySubject, replyText; - QuickReplyViewHolder(View quickReply) { + QuickReplyViewHolder(View quickReply, CustomEditTextListener replySubject + , CustomEditTextListener replyText) { super(quickReply); + thumbnail = (ImageView) quickReply.findViewById(R.id.thumbnail); + username = (TextView) quickReply.findViewById(R.id.username); this.quickReply = (EditText) quickReply.findViewById(R.id.quick_reply_text); + this.replyText = replyText; + this.quickReply.addTextChangedListener(replyText); + quickReplySubject = (EditText) quickReply.findViewById(R.id.quick_reply_subject); + this.replySubject = replySubject; + quickReplySubject.addTextChangedListener(replySubject); submitButton = (AppCompatImageButton) quickReply.findViewById(R.id.quick_reply_submit); } } @@ -684,6 +684,82 @@ class TopicAdapter extends RecyclerView.Adapter { } + private class CustomEditTextListener implements TextWatcher { + private 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) { + } + } + + @Nullable + private String buildQuote(int quotePosition) { + Date postDate = null; + { + String date = postsList.get(quotePosition).getPostDate(); + if (date != null) { + DateFormat format = new SimpleDateFormat("MMMM d, yyyy, h:m:s a", Locale.ENGLISH); + date = date.replace("Ιανουαρίου", "January"); + date = date.replace("Φεβρουαρίου", "February"); + date = date.replace("Μαρτίου", "March"); + date = date.replace("Απριλίου", "April"); + date = date.replace("Μαΐου", "May"); + date = date.replace("Ιουνίου", "June"); + date = date.replace("Ιουλίου", "July"); + date = date.replace("Αυγούστου", "August"); + date = date.replace("Σεπτεμβρίου", "September"); + date = date.replace("Οκτωβρίου", "October"); + date = date.replace("Νοεμβρίου", "November"); + date = date.replace("Δεκεμβρίου", "December"); + + if (date.contains("Today")) { + date = date.replace("Today at", + Calendar.getInstance().getDisplayName(Calendar.MONTH, + Calendar.LONG, Locale.ENGLISH) + + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); + } else if (date.contains("Σήμερα")) { + date = date.replace("Σήμερα στις", + Calendar.getInstance().getDisplayName(Calendar.MONTH, + Calendar.LONG, Locale.ENGLISH) + + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); + if (date.contains("πμ")) date = date.replace("πμ", "am"); + if (date.contains("μμ")) date = date.replace("μμ", "pm"); + } + try { + postDate = format.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + } + } + } + if (postsList.get(quotePosition).getPostIndex() != 0) { + if (postDate != null) { + return "[quote author=" + postsList.get(quotePosition).getAuthor() + + " link=topic=68525.msg" + postsList.get(quotePosition).getPostIndex() + + "#msg" + postsList.get(quotePosition).getPostIndex() + + " date=" + postDate.getTime() / 1000 + "]" + + "\n" + postsList.get(quotePosition).getContent() + + "\n" + "[/quote]" + "\n\n"; + } + } + return null; + } + /** * Returns a String with a single FontAwesome typeface character corresponding to this file's * extension. diff --git a/app/src/main/res/layout/activity_topic_post_row.xml b/app/src/main/res/layout/activity_topic_post_row.xml index 13e53227..677a58db 100644 --- a/app/src/main/res/layout/activity_topic_post_row.xml +++ b/app/src/main/res/layout/activity_topic_post_row.xml @@ -115,8 +115,7 @@ android:layout_toEndOf="@+id/thumbnail_holder" android:ellipsize="end" android:maxLines="1" - android:text="@string/post_subject" - /> + android:text="@string/post_subject"/> @@ -22,32 +23,94 @@ + android:orientation="vertical"> - + android:clickable="true" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="16dp"> + + + + + + + - + android:layout_below="@+id/username" + android:layout_toEndOf="@+id/thumbnail_holder" + android:hint="@string/quick_reply_subject" + android:inputType="textMultiLine" + android:maxLength="80" + android:textSize="10sp"/> + - + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1d04389..d95e5e63 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ next last Quick reply… + Subject… Submit