Replying to messages is now available. Remove CollapsingToolbarLayout in ViewPrivateMessagesActivity. Scroll to bottom when entering ViewPrivateMessagesActivity.

This commit is contained in:
Alex Ning 2020-07-01 13:38:25 +08:00
parent a00f7ced6c
commit 707316c7ad
10 changed files with 326 additions and 130 deletions

View File

@ -173,7 +173,7 @@ public interface RedditAPI {
@FormUrlEncoded
@POST("api/comment")
Call<String> sendComment(@HeaderMap Map<String, String> headers, @FieldMap Map<String, String> params);
Call<String> sendCommentOrReplyToMessage(@HeaderMap Map<String, String> headers, @FieldMap Map<String, String> params);
@FormUrlEncoded
@POST("api/del")

View File

@ -20,7 +20,7 @@ import androidx.transition.AutoTransition;
import androidx.transition.TransitionManager;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.snackbar.Snackbar;
import com.r0adkll.slidr.Slidr;
import java.util.ArrayList;
@ -39,6 +39,7 @@ import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.Message;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
import ml.docilealligator.infinityforreddit.ReplyMessage;
import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils;
import retrofit2.Retrofit;
@ -53,8 +54,6 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
LinearLayout mLinearLayout;
@BindView(R.id.coordinator_layout_view_private_messages_activity)
CoordinatorLayout mCoordinatorLayout;
@BindView(R.id.collapsing_toolbar_layout_view_private_messages_activity)
CollapsingToolbarLayout collapsingToolbarLayout;
@BindView(R.id.appbar_layout_view_private_messages_activity)
AppBarLayout mAppBarLayout;
@BindView(R.id.toolbar_view_private_messages_activity)
@ -156,6 +155,46 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
mLinearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setAdapter(mAdapter);
goToBottom();
mSendImageView.setOnClickListener(view -> {
if (!mEditText.getText().toString().equals("")) {
//Send Message
if (privateMessage != null) {
Message replyTo;
ArrayList<Message> replies = privateMessage.getReplies();
if (replies != null && !replies.isEmpty()) {
replyTo = privateMessage;
} else {
replyTo = replies.get(replies.size() - 1);
}
if (replyTo != null) {
ReplyMessage.replyMessage(mEditText.getText().toString(), replyTo.getFullname(),
getResources().getConfiguration().locale, mOauthRetrofit, mAccessToken,
new ReplyMessage.ReplyMessageListener() {
@Override
public void replyMessageSuccess(Message message) {
if (mAdapter != null) {
mAdapter.addReply(message);
}
goToBottom();
mEditText.setText("");
}
@Override
public void replyMessageFailed(String errorMessage) {
if (errorMessage != null && !errorMessage.equals("")) {
Snackbar.make(mCoordinatorLayout, errorMessage, Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(mCoordinatorLayout, R.string.reply_message_failed, Snackbar.LENGTH_LONG).show();
}
}
});
} else {
Snackbar.make(mCoordinatorLayout, R.string.error_getting_message, Snackbar.LENGTH_LONG).show();
}
}
}
});
}
public void fetchUserAvatar(String username, ProvideUserAvatarCallback provideUserAvatarCallback) {
@ -180,6 +219,12 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
TransitionManager.beginDelayedTransition(mRecyclerView, new AutoTransition());
}
private void goToBottom() {
if (mLinearLayoutManager != null && mAdapter != null) {
mLinearLayoutManager.scrollToPositionWithOffset(mAdapter.getItemCount() - 1, 0);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {

View File

@ -1,23 +1,13 @@
package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import ml.docilealligator.infinityforreddit.API.RedditAPI;
import ml.docilealligator.infinityforreddit.Utils.APIUtils;
import ml.docilealligator.infinityforreddit.Utils.JSONUtils;
import ml.docilealligator.infinityforreddit.Utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -42,8 +32,7 @@ public class FetchMessages {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
if (response.isSuccessful()) {
new ParseMessageAsnycTask(response.body(), locale, messageType,
fetchMessagesListener::fetchSuccess).execute();
ParseMessage.parseMessage(response.body(), locale, messageType, fetchMessagesListener::fetchSuccess);
} else {
fetchMessagesListener.fetchFailed();
}
@ -56,104 +45,9 @@ public class FetchMessages {
});
}
static ArrayList<Message> parseMessage(JSONArray messageArray, Locale locale, int messageType) {
ArrayList<Message> messages = new ArrayList<>();
for (int i = 0; i < messageArray.length(); i++) {
try {
JSONObject messageJSON = messageArray.getJSONObject(i);
String kind = messageJSON.getString(JSONUtils.KIND_KEY);
if ((messageType == MESSAGE_TYPE_NOTIFICATION && kind.equals("t4")) ||
(messageType == MESSAGE_TYPE_PRIVATE_MESSAGE && !kind.equals("t4"))) {
continue;
}
JSONObject rawMessageJSON = messageJSON.getJSONObject(JSONUtils.DATA_KEY);
String subredditName = rawMessageJSON.getString(JSONUtils.SUBREDDIT_KEY);
String subredditNamePrefixed = rawMessageJSON.getString(JSONUtils.SUBREDDIT_NAME_PREFIX_KEY);
String id = rawMessageJSON.getString(JSONUtils.ID_KEY);
String fullname = rawMessageJSON.getString(JSONUtils.NAME_KEY);
String subject = rawMessageJSON.getString(JSONUtils.SUBJECT_KEY);
String author = rawMessageJSON.getString(JSONUtils.AUTHOR_KEY);
String parentFullname = rawMessageJSON.getString(JSONUtils.PARENT_ID_KEY);
String title = rawMessageJSON.has(JSONUtils.LINK_TITLE_KEY) ? rawMessageJSON.getString(JSONUtils.LINK_TITLE_KEY) : null;
String body = Utils.modifyMarkdown(rawMessageJSON.getString(JSONUtils.BODY_KEY));
String context = rawMessageJSON.getString(JSONUtils.CONTEXT_KEY);
String distinguished = rawMessageJSON.getString(JSONUtils.DISTINGUISHED_KEY);
boolean wasComment = rawMessageJSON.getBoolean(JSONUtils.WAS_COMMENT_KEY);
boolean isNew = rawMessageJSON.getBoolean(JSONUtils.NEW_KEY);
int score = rawMessageJSON.getInt(JSONUtils.SCORE_KEY);
int nComments = rawMessageJSON.isNull(JSONUtils.NUM_COMMENTS_KEY) ? -1 : rawMessageJSON.getInt(JSONUtils.NUM_COMMENTS_KEY);
long timeUTC = rawMessageJSON.getLong(JSONUtils.CREATED_UTC_KEY) * 1000;
Calendar submitTimeCalendar = Calendar.getInstance();
submitTimeCalendar.setTimeInMillis(timeUTC);
String formattedTime = new SimpleDateFormat("MMM d, yyyy, HH:mm",
locale).format(submitTimeCalendar.getTime());
ArrayList<Message> replies = null;
if (!rawMessageJSON.isNull(JSONUtils.REPLIES_KEY) && rawMessageJSON.get(JSONUtils.REPLIES_KEY) instanceof JSONObject) {
JSONArray repliesArray = rawMessageJSON.getJSONObject(JSONUtils.REPLIES_KEY).getJSONObject(JSONUtils.DATA_KEY)
.getJSONArray(JSONUtils.CHILDREN_KEY);
replies = parseMessage(repliesArray, locale, messageType);
}
Message message = new Message(kind, subredditName, subredditNamePrefixed, id, fullname, subject,
author, parentFullname, title, body, context, distinguished, formattedTime,
wasComment, isNew, score, nComments, timeUTC);
if (replies != null) {
message.setReplies(replies);
}
messages.add(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
return messages;
}
interface FetchMessagesListener {
void fetchSuccess(ArrayList<Message> messages, @Nullable String after);
void fetchFailed();
}
private static class ParseMessageAsnycTask extends AsyncTask<Void, Void, Void> {
private String response;
private Locale locale;
private ArrayList<Message> messages;
private String after;
private int messageType;
private ParseMessageAsyncTaskListener parseMessageAsyncTaskListener;
ParseMessageAsnycTask(String response, Locale locale, int messageType,
ParseMessageAsyncTaskListener parseMessageAsnycTaskListener) {
this.response = response;
this.locale = locale;
this.messageType = messageType;
messages = new ArrayList<>();
this.parseMessageAsyncTaskListener = parseMessageAsnycTaskListener;
}
@Override
protected Void doInBackground(Void... voids) {
try {
JSONArray messageArray = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
messages = parseMessage(messageArray, locale, messageType);
after = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY).getString(JSONUtils.AFTER_KEY);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
parseMessageAsyncTaskListener.parseSuccess(messages, after);
}
interface ParseMessageAsyncTaskListener {
void parseSuccess(ArrayList<Message> messages, @Nullable String after);
}
}
}

View File

@ -0,0 +1,205 @@
package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import ml.docilealligator.infinityforreddit.Utils.JSONUtils;
import ml.docilealligator.infinityforreddit.Utils.Utils;
public class ParseMessage {
public static void parseMessage(String response, Locale locale, int messageType,
ParseMessageAsyncTaskListener parseMessageAsnycTaskListener) {
new ParseMessageAsnycTask(response, locale, messageType, parseMessageAsnycTaskListener).execute();
}
public static ArrayList<Message> parseMessages(JSONArray messageArray, Locale locale, int messageType) {
ArrayList<Message> messages = new ArrayList<>();
for (int i = 0; i < messageArray.length(); i++) {
try {
Message message = parseSingleMessage(messageArray.getJSONObject(i), locale, messageType);
if (message != null) {
messages.add(message);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return messages;
}
public static void parseRepliedMessage(String response, Locale locale, ParseSentMessageAsyncTaskListener parseSentMessageAsyncTaskListener) {
new ParseSentMessageAsnycTask(response, locale, parseSentMessageAsyncTaskListener).execute();
}
@Nullable
private static Message parseSingleMessage(JSONObject messageJSON, Locale locale, int messageType) throws JSONException {
String kind = messageJSON.getString(JSONUtils.KIND_KEY);
if ((messageType == FetchMessages.MESSAGE_TYPE_NOTIFICATION && kind.equals("t4")) ||
(messageType == FetchMessages.MESSAGE_TYPE_PRIVATE_MESSAGE && !kind.equals("t4"))) {
return null;
}
JSONObject rawMessageJSON = messageJSON.getJSONObject(JSONUtils.DATA_KEY);
String subredditName = rawMessageJSON.getString(JSONUtils.SUBREDDIT_KEY);
String subredditNamePrefixed = rawMessageJSON.getString(JSONUtils.SUBREDDIT_NAME_PREFIX_KEY);
String id = rawMessageJSON.getString(JSONUtils.ID_KEY);
String fullname = rawMessageJSON.getString(JSONUtils.NAME_KEY);
String subject = rawMessageJSON.getString(JSONUtils.SUBJECT_KEY);
String author = rawMessageJSON.getString(JSONUtils.AUTHOR_KEY);
String parentFullname = rawMessageJSON.getString(JSONUtils.PARENT_ID_KEY);
String title = rawMessageJSON.has(JSONUtils.LINK_TITLE_KEY) ? rawMessageJSON.getString(JSONUtils.LINK_TITLE_KEY) : null;
String body = Utils.modifyMarkdown(rawMessageJSON.getString(JSONUtils.BODY_KEY));
String context = rawMessageJSON.getString(JSONUtils.CONTEXT_KEY);
String distinguished = rawMessageJSON.getString(JSONUtils.DISTINGUISHED_KEY);
boolean wasComment = rawMessageJSON.getBoolean(JSONUtils.WAS_COMMENT_KEY);
boolean isNew = rawMessageJSON.getBoolean(JSONUtils.NEW_KEY);
int score = rawMessageJSON.getInt(JSONUtils.SCORE_KEY);
int nComments = rawMessageJSON.isNull(JSONUtils.NUM_COMMENTS_KEY) ? -1 : rawMessageJSON.getInt(JSONUtils.NUM_COMMENTS_KEY);
long timeUTC = rawMessageJSON.getLong(JSONUtils.CREATED_UTC_KEY) * 1000;
Calendar submitTimeCalendar = Calendar.getInstance();
submitTimeCalendar.setTimeInMillis(timeUTC);
String formattedTime = new SimpleDateFormat("MMM d, yyyy, HH:mm",
locale).format(submitTimeCalendar.getTime());
ArrayList<Message> replies = null;
if (!rawMessageJSON.isNull(JSONUtils.REPLIES_KEY) && rawMessageJSON.get(JSONUtils.REPLIES_KEY) instanceof JSONObject) {
JSONArray repliesArray = rawMessageJSON.getJSONObject(JSONUtils.REPLIES_KEY).getJSONObject(JSONUtils.DATA_KEY)
.getJSONArray(JSONUtils.CHILDREN_KEY);
replies = parseMessages(repliesArray, locale, messageType);
}
Message message = new Message(kind, subredditName, subredditNamePrefixed, id, fullname, subject,
author, parentFullname, title, body, context, distinguished, formattedTime,
wasComment, isNew, score, nComments, timeUTC);
if (replies != null) {
message.setReplies(replies);
}
return message;
}
private static class ParseMessageAsnycTask extends AsyncTask<Void, Void, Void> {
private String response;
private Locale locale;
private ArrayList<Message> messages;
private String after;
private int messageType;
private ParseMessageAsyncTaskListener parseMessageAsyncTaskListener;
ParseMessageAsnycTask(String response, Locale locale, int messageType,
ParseMessageAsyncTaskListener parseMessageAsnycTaskListener) {
this.response = response;
this.locale = locale;
this.messageType = messageType;
messages = new ArrayList<>();
this.parseMessageAsyncTaskListener = parseMessageAsnycTaskListener;
}
@Override
protected Void doInBackground(Void... voids) {
try {
JSONArray messageArray = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
messages = parseMessages(messageArray, locale, messageType);
after = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY).getString(JSONUtils.AFTER_KEY);
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
parseMessageAsyncTaskListener.parseSuccess(messages, after);
}
}
private static class ParseSentMessageAsnycTask extends AsyncTask<Void, Void, Void> {
private String response;
private Locale locale;
private Message message;
private String errorMessage;
private boolean parseFailed = false;
private ParseSentMessageAsyncTaskListener parseSentMessageAsyncTaskListener;
ParseSentMessageAsnycTask(String response, Locale locale, ParseSentMessageAsyncTaskListener parseSentMessageAsyncTaskListener) {
this.response = response;
this.locale = locale;
this.parseSentMessageAsyncTaskListener = parseSentMessageAsyncTaskListener;
}
@Override
protected Void doInBackground(Void... voids) {
try {
JSONObject messageJSON = new JSONObject(response).getJSONObject(JSONUtils.JSON_KEY)
.getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.THINGS_KEY).getJSONObject(0);
message = parseSingleMessage(messageJSON, locale, FetchMessages.MESSAGE_TYPE_PRIVATE_MESSAGE);
} catch (JSONException e) {
e.printStackTrace();
errorMessage = parseRepliedMessageErrorMessage(response);
parseFailed = true;
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (parseFailed) {
parseSentMessageAsyncTaskListener.parseFailed(errorMessage);
} else {
parseSentMessageAsyncTaskListener.parseSuccess(message);
}
}
}
@Nullable
private static String parseRepliedMessageErrorMessage(String response) {
try {
JSONObject responseObject = new JSONObject(response).getJSONObject(JSONUtils.JSON_KEY);
if (responseObject.getJSONArray(JSONUtils.ERRORS_KEY).length() != 0) {
JSONArray error = responseObject.getJSONArray(JSONUtils.ERRORS_KEY)
.getJSONArray(responseObject.getJSONArray(JSONUtils.ERRORS_KEY).length() - 1);
if (error.length() != 0) {
String errorString;
if (error.length() >= 2) {
errorString = error.getString(1);
} else {
errorString = error.getString(0);
}
return errorString.substring(0, 1).toUpperCase() + errorString.substring(1);
} else {
return null;
}
} else {
return null;
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public interface ParseMessageAsyncTaskListener {
void parseSuccess(ArrayList<Message> messages, @Nullable String after);
}
public interface ParseSentMessageAsyncTaskListener {
void parseSuccess(Message message);
void parseFailed(String errorMessage);
}
}

View File

@ -84,7 +84,7 @@ public class PullNotificationWorker extends Worker {
if (response != null && response.isSuccessful() && response.body() != null) {
String responseBody = response.body();
JSONArray messageArray = new JSONObject(responseBody).getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
ArrayList<Message> messages = FetchMessages.parseMessage(messageArray,
ArrayList<Message> messages = ParseMessage.parseMessages(messageArray,
context.getResources().getConfiguration().locale, FetchMessages.MESSAGE_TYPE_NOTIFICATION);
if (!messages.isEmpty()) {

View File

@ -0,0 +1,58 @@
package ml.docilealligator.infinityforreddit;
import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import ml.docilealligator.infinityforreddit.API.RedditAPI;
import ml.docilealligator.infinityforreddit.Utils.APIUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class ReplyMessage {
public static void replyMessage(String messageMarkdown, String thingFullname,
Locale locale, Retrofit oauthRetrofit, String accessToken,
ReplyMessageListener replyMessageListener) {
Map<String, String> headers = APIUtils.getOAuthHeader(accessToken);
Map<String, String> params = new HashMap<>();
params.put(APIUtils.API_TYPE_KEY, "json");
params.put(APIUtils.RETURN_RTJSON_KEY, "true");
params.put(APIUtils.TEXT_KEY, messageMarkdown);
params.put(APIUtils.THING_ID_KEY, thingFullname);
oauthRetrofit.create(RedditAPI.class).sendCommentOrReplyToMessage(headers, params).enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
if (response.isSuccessful()) {
ParseMessage.parseRepliedMessage(response.body(), locale, new ParseMessage.ParseSentMessageAsyncTaskListener() {
@Override
public void parseSuccess(Message message) {
replyMessageListener.replyMessageSuccess(message);
}
@Override
public void parseFailed(String errorMessage) {
replyMessageListener.replyMessageFailed(errorMessage);
}
});
} else {
replyMessageListener.replyMessageFailed(response.message());
}
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
replyMessageListener.replyMessageFailed(t.getMessage());
}
});
}
public interface ReplyMessageListener {
void replyMessageSuccess(Message message);
void replyMessageFailed(String errorMessage);
}
}

View File

@ -26,9 +26,9 @@ public class SendComment {
params.put(APIUtils.RETURN_RTJSON_KEY, "true");
params.put(APIUtils.TEXT_KEY, commentMarkdown);
params.put(APIUtils.THING_ID_KEY, thingFullname);
api.sendComment(headers, params);
api.sendCommentOrReplyToMessage(headers, params);
Call<String> sendCommentCall = api.sendComment(headers, params);
Call<String> sendCommentCall = api.sendCommentOrReplyToMessage(headers, params);
sendCommentCall.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {

View File

@ -112,4 +112,5 @@ public class JSONUtils {
public static final String WEBM_URL_KEY = "webmUrl";
public static final String TYPE_KEY = "type";
public static final String MP4_KEY = "mp4";
public static final String THINGS_KEY = "things";
}

View File

@ -20,23 +20,13 @@
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout_view_private_messages_activity"
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_view_private_messages_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlways"
app:titleEnabled="false"
app:toolbarId="@+id/toolbar_view_private_messages_activity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_view_private_messages_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:navigationIcon="?attr/homeAsUpIndicator" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:navigationIcon="?attr/homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -152,6 +152,9 @@
<string name="send_comment_failed">Could not send this comment</string>
<string name="parse_sent_comment_failed">The comment is sent but unable to get the sent comment</string>
<string name="reply_message_failed">Could not reply to this message</string>
<string name="error_getting_message">Error getting this message</string>
<string name="select_a_subreddit">Please select a subreddit first</string>
<string name="title_required">The post need a good title</string>
<string name="link_required">Hey where is the link?</string>