diff options
Diffstat (limited to '')
10 files changed, 505 insertions, 0 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 7b054b03..95e8f658 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -34,4 +34,5 @@ interface AppComponent { void inject(EditPostActivity editPostActivity); void inject(EditCommentActivity editCommentActivity); void inject(AccountPostsActivity accountPostsActivity); + void inject(PullNotificationWorker pullNotificationWorker); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java new file mode 100644 index 00000000..04d6f8de --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java @@ -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); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java index 78e3659c..8d6539ec 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java @@ -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"; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java index a091cef0..d95a210e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java @@ -132,4 +132,8 @@ public class LinkResolverActivity extends AppCompatActivity { } return packagesSupportingCustomTabs; } + + static Uri getRedditUriByPath(String path) { + return Uri.parse("https://www.reddit.com" + path); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java index 11717ed7..5309d7b5 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java @@ -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(); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java new file mode 100644 index 00000000..1846a8ef --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java @@ -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; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java index 978ebe0f..89c92a0c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java @@ -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); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java new file mode 100644 index 00000000..f166f1b5 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java @@ -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(); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java index a9caccd6..ddd53c47 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java @@ -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); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46a3899b..359315a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -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> |