Fetch unread messages periodically (15 mins for test) using WorkManager. Click new comment notifications to view new comment in ViewPostDetailActivity.

This commit is contained in:
Alex Ning 2019-08-17 17:43:29 +08:00
parent 627bad649b
commit 21c9eb03ed
13 changed files with 506 additions and 0 deletions

Binary file not shown.

Binary file not shown.

View File

@ -79,4 +79,5 @@ dependencies {
implementation 'com.github.livefront:bridge:v1.2.0'
implementation 'com.evernote:android-state:1.4.1'
annotationProcessor 'com.evernote:android-state-processor:1.4.1'
implementation "androidx.work:work-runtime:2.2.0"
}

View File

@ -34,4 +34,5 @@ interface AppComponent {
void inject(EditPostActivity editPostActivity);
void inject(EditCommentActivity editCommentActivity);
void inject(AccountPostsActivity accountPostsActivity);
void inject(PullNotificationWorker pullNotificationWorker);
}

View File

@ -0,0 +1,134 @@
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 retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
class FetchMessages {
interface FetchMessagesListener {
void fetchSuccess(@Nullable ArrayList<Message> messages);
void fetchFailed(boolean shouldRetry);
}
static final String WHERE_INBOX = "inbox";
static final String WHERE_UNREAD = "unread";
static final String WHERE_SENT = "sent";
static final String WHERE_COMMENTS = "comments";
static void fetchMessagesAsync(Retrofit oauthRetrofit, Locale locale, String accessToken, String where,
FetchMessagesListener fetchMessagesListener) {
oauthRetrofit.create(RedditAPI.class).getMessages(RedditUtils.getOAuthHeader(accessToken), where)
.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
if(response.isSuccessful()) {
new ParseMessageAsnycTask(response.body(), locale,
fetchMessagesListener::fetchSuccess).execute();
} else {
fetchMessagesListener.fetchFailed(true);
}
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
fetchMessagesListener.fetchFailed(true);
}
});
}
@Nullable
static ArrayList<Message> parseMessage(String response, Locale locale) {
JSONArray messageArray;
try {
messageArray = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
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);
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 = 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());
messages.add(new Message(kind, subredditName, subredditNamePrefixed, id, fullname, subject,
author, parentFullname, title, body, context, distinguished, formattedTime,
wasComment, isNew, score, nComments, timeUTC));
} catch (JSONException e) {
e.printStackTrace();
}
}
return messages;
}
private static class ParseMessageAsnycTask extends AsyncTask<Void, Void, Void> {
interface ParseMessageAsyncTaskListener {
void parseSuccess(ArrayList<Message> messages);
}
private String response;
private Locale locale;
private ArrayList<Message> messages;
private ParseMessageAsyncTaskListener parseMessageAsyncTaskListener;
ParseMessageAsnycTask(String response, Locale locale, ParseMessageAsyncTaskListener parseMessageAsnycTaskListener) {
this.response = response;
this.locale = locale;
messages = new ArrayList<>();
this.parseMessageAsyncTaskListener = parseMessageAsnycTaskListener;
}
@Override
protected Void doInBackground(Void... voids) {
messages = parseMessage(response, locale);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
parseMessageAsyncTaskListener.parseSuccess(messages);
}
}
}

View File

@ -5,6 +5,7 @@ package ml.docilealligator.infinityforreddit;
*/
public class JSONUtils {
static final String KIND_KEY = "kind";
static final String DATA_KEY = "data";
static final String AFTER_KEY = "after";
static final String CHILDREN_KEY = "children";
@ -62,6 +63,7 @@ public class JSONUtils {
static final String JSON_KEY = "json";
static final String PARENT_ID_KEY = "parent_id";
static final String LINK_ID_KEY = "link_id";
static final String LINK_TITLE_KEY = "link_title";
static final String ERRORS_KEY = "errors";
static final String ARGS_KEY = "args";
static final String FIELDS_KEY = "fields";
@ -73,4 +75,10 @@ public class JSONUtils {
static final String DESCRIPTION_HTML_KEY = "description_html";
static final String ARCHIVED_KEY = "archived";
static final String TEXT_EDITABLE_KEY = "text_editable";
static final String SUBJECT_KEY = "subject";
static final String CONTEXT_KEY = "context";
static final String DISTINGUISHED_KEY = "distinguished";
static final String WAS_COMMENT_KEY = "was_comment";
static final String NEW_KEY = "new";
static final String NUM_COMMENTS_KEY = "num_comments";
}

View File

@ -132,4 +132,8 @@ public class LinkResolverActivity extends AppCompatActivity {
}
return packagesSupportingCustomTabs;
}
static Uri getRedditUriByPath(String path) {
return Uri.parse("https://www.reddit.com" + path);
}
}

View File

@ -30,6 +30,10 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import androidx.work.Constraints;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
@ -39,6 +43,8 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
@ -234,6 +240,17 @@ public class MainActivity extends AppCompatActivity implements SortTypeBottomShe
mProfileImageUrl = account.getProfileImageUrl();
mBannerImageUrl = account.getBannerImageUrl();
mKarma = account.getKarma();
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest pullNotificationRequest =
new PeriodicWorkRequest.Builder(PullNotificationWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
WorkManager.getInstance(this).enqueue(pullNotificationRequest);
}
bindView();
}).execute();

View File

@ -0,0 +1,126 @@
package ml.docilealligator.infinityforreddit;
class Message {
static final String TYPE_COMMENT = "t1";
static final String TYPE_ACCOUNT = "t2";
static final String TYPE_LINK = "t3";
static final String TYPE_MESSAGE = "t4";
static final String TYPE_SUBREDDIT = "t5";
static final String TYPE_AWARD = "t6";
private String kind;
private String subredditName;
private String subredditNamePrefixed;
private String id;
private String fullname;
private String subject;
private String author;
private String parentFullName;
private String title;
private String body;
private String context;
private String distinguished;
private String formattedTime;
private boolean wasComment;
private boolean isNew;
private int score;
private int nComments;
private long timeUTC;
Message(String kind, String subredditName, String subredditNamePrefixed, String id, String fullname,
String subject, String author, String parentFullName, String title, String body, String context,
String distinguished, String formattedTime, boolean wasComment, boolean isNew, int score,
int nComments, long timeUTC) {
this.kind = kind;
this.subredditName = subredditName;
this.subredditNamePrefixed = subredditNamePrefixed;
this.id = id;
this.fullname = fullname;
this.subject = subject;
this.author = author;
this.parentFullName = parentFullName;
this.title = title;
this.body = body;
this.context = context;
this.distinguished = distinguished;
this.formattedTime = formattedTime;
this.wasComment = wasComment;
this.isNew = isNew;
this.score = score;
this.nComments = nComments;
this.timeUTC = timeUTC;
}
public String getKind() {
return kind;
}
public String getSubredditName() {
return subredditName;
}
public String getSubredditNamePrefixed() {
return subredditNamePrefixed;
}
public String getId() {
return id;
}
public String getFullname() {
return fullname;
}
public String getSubject() {
return subject;
}
public String getAuthor() {
return author;
}
public String getParentFullName() {
return parentFullName;
}
public String getTitle() {
return title;
}
public String getBody() {
return body;
}
public String getContext() {
return context;
}
public String getDistinguished() {
return distinguished;
}
public String getFormattedTime() {
return formattedTime;
}
public boolean isWasComment() {
return wasComment;
}
public boolean isNew() {
return isNew;
}
public int getScore() {
return score;
}
public int getnComments() {
return nComments;
}
public long getTimeUTC() {
return timeUTC;
}
}

View File

@ -1,5 +1,65 @@
package ml.docilealligator.infinityforreddit;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
class NotificationUtils {
static final String CHANNEL_POST_MEDIA = "Post Media";
static final String CHANNEL_ID_NEW_COMMENTS = "new_comments";
static final String CHANNEL_NEW_COMMENTS = "New Comments";
static final String GROUP_NEW_COMMENTS = "ml.docilealligator.infinityforreddit.NEW_COMMENTS";
static final int SUMMARY_ID_NEW_COMMENTS = 0;
static final int BASE_ID_COMMENT = 1;
static final int BASE_ID_ACCOUNT = 1000;
static final int BASE_ID_POST = 2000;
static final int BASE_ID_MESSAGE = 3000;
static final int BASE_ID_SUBREDDIT = 4000;
static final int BASE_ID_AWARD = 5000;
static NotificationCompat.Builder buildNotification(NotificationManagerCompat notificationManager,
Context context, String title, String content,
String summary, String channelId, String channelName,
String group) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
return new NotificationCompat.Builder(context.getApplicationContext(), channelId)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher)
.setStyle(new NotificationCompat.BigTextStyle()
.setSummaryText(summary)
.bigText(content))
.setGroup(group)
.setAutoCancel(true);
}
static NotificationCompat.Builder buildSummaryNotification(Context context, NotificationManagerCompat notificationManager,
String title, String content, String channelId,
String channelName, String group) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
return new NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
//set content text to support devices running API level < 24
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher)
.setGroup(group)
.setGroupSummary(true)
.setAutoCancel(true);
}
static NotificationManagerCompat getNotificationManager(Context context) {
return NotificationManagerCompat.from(context);
}
}

View File

@ -0,0 +1,144 @@
package ml.docilealligator.infinityforreddit;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.IOException;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import Account.Account;
import retrofit2.Response;
import retrofit2.Retrofit;
public class PullNotificationWorker extends Worker {
private Context context;
@Inject
@Named("oauth")
Retrofit mOauthRetrofit;
@Inject
RedditDataRoomDatabase redditDataRoomDatabase;
public PullNotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
this.context = context;
((Infinity) context.getApplicationContext()).getAppComponent().inject(this);
}
@NonNull
@Override
public Result doWork() {
Log.i("workmanager", "do");
try {
Log.i("workmanager", "before response");
Account currentAccount = redditDataRoomDatabase.accountDao().getCurrentAccount();
Response<String> response = mOauthRetrofit.create(RedditAPI.class).getMessages(
RedditUtils.getOAuthHeader(currentAccount.getAccessToken()),
FetchMessages.WHERE_COMMENTS).execute();
Log.i("workmanager", "has response");
if(response.isSuccessful()) {
String responseBody = response.body();
ArrayList<Message> messages = FetchMessages.parseMessage(responseBody, context.getResources().getConfiguration().locale);
if(messages != null) {
NotificationManagerCompat notificationManager = NotificationUtils.getNotificationManager(context);
NotificationCompat.Builder summaryBuilder = NotificationUtils.buildSummaryNotification(context,
notificationManager, currentAccount.getUsername(), messages.size() + " new comment replies",
NotificationUtils.CHANNEL_ID_NEW_COMMENTS, NotificationUtils.CHANNEL_NEW_COMMENTS,
NotificationUtils.GROUP_NEW_COMMENTS);
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
int messageSize = messages.size() >= 5 ? 5 : messages.size();
for(int i = messageSize - 1; i >= 0; i--) {
Message message = messages.get(i);
inboxStyle.addLine(message.getAuthor() + " " + message.getBody());
String kind = message.getKind();
String title;
String summary;
if(kind.equals(Message.TYPE_COMMENT)) {
title = message.getAuthor();
summary = context.getString(R.string.notification_summary_comment);
} else {
title = message.getTitle();
if(kind.equals(Message.TYPE_ACCOUNT)) {
summary = context.getString(R.string.notification_summary_account);
} else if(kind.equals(Message.TYPE_LINK)) {
summary = context.getString(R.string.notification_summary_post);
} else if(kind.equals(Message.TYPE_MESSAGE)) {
summary = context.getString(R.string.notification_summary_message);
} else if(kind.equals(Message.TYPE_SUBREDDIT)) {
summary = context.getString(R.string.notification_summary_subreddit);
} else {
summary = context.getString(R.string.notification_summary_award);
}
}
NotificationCompat.Builder builder = NotificationUtils.buildNotification(notificationManager,
context, title, message.getBody(), summary,
NotificationUtils.CHANNEL_ID_NEW_COMMENTS,
NotificationUtils.CHANNEL_NEW_COMMENTS, NotificationUtils.GROUP_NEW_COMMENTS);
if(kind.equals(Message.TYPE_COMMENT)) {
Intent intent = new Intent(context, LinkResolverActivity.class);
Uri uri = LinkResolverActivity.getRedditUriByPath(message.getContext());
intent.setData(uri);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
builder.setContentIntent(pendingIntent);
notificationManager.notify(NotificationUtils.BASE_ID_COMMENT + i, builder.build());
} else if(kind.equals(Message.TYPE_ACCOUNT)) {
notificationManager.notify(NotificationUtils.BASE_ID_ACCOUNT + i, builder.build());
} else if(kind.equals(Message.TYPE_LINK)) {
notificationManager.notify(NotificationUtils.BASE_ID_POST + i, builder.build());
} else if(kind.equals(Message.TYPE_MESSAGE)) {
notificationManager.notify(NotificationUtils.BASE_ID_MESSAGE + i, builder.build());
} else if(kind.equals(Message.TYPE_SUBREDDIT)) {
notificationManager.notify(NotificationUtils.BASE_ID_SUBREDDIT + i, builder.build());
} else {
notificationManager.notify(NotificationUtils.BASE_ID_AWARD + i, builder.build());
}
}
inboxStyle.setBigContentTitle(messages.size() + " New Messages")
.setSummaryText(currentAccount.getUsername());
summaryBuilder.setStyle(inboxStyle);
notificationManager.notify(NotificationUtils.SUMMARY_ID_NEW_COMMENTS, summaryBuilder.build());
Log.i("workmanager", "message size " + messages.size());
} else {
Log.i("workmanager", "retry1");
return Result.retry();
}
} else {
Log.i("workmanager", "retry2 " + response.code());
return Result.retry();
}
} catch (IOException e) {
e.printStackTrace();
Log.i("workmanager", "retry3");
return Result.retry();
}
Log.i("workmanager", "success");
return Result.success();
}
}

View File

@ -156,4 +156,7 @@ public interface RedditAPI {
@FormUrlEncoded
@POST("{subredditNamePrefixed}/api/selectflair")
Call<String> selectFlair(@Path("subredditNamePrefixed") String subredditName, @HeaderMap Map<String, String> headers, @FieldMap Map<String, String> params);
@GET("/message/{where}.json?raw_json=1")
Call<String> getMessages(@HeaderMap Map<String, String> headers, @Path("where") String where);
}

View File

@ -218,4 +218,12 @@
<string name="only_allow_64_chars">Only allow less than 64 characters</string>
<string name="view_all_comments">Click here to browse all comments</string>
<string name="notification_summary_comment">New Comments</string>
<string name="notification_summary_account">Account</string>
<string name="notification_summary_post">Post</string>
<string name="notification_summary_message">New Messages</string>
<string name="notification_summary_subreddit">Subreddit</string>
<string name="notification_summary_award">Award</string>
<string name="notification_new_messages">%1$d New Messages</string>
</resources>