diff options
30 files changed, 939 insertions, 111 deletions
diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 144d0da4..dd08719b 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -35,9 +35,9 @@ <map> <entry key="assetSourceType" value="FILE" /> <entry key="color" value="ffffff" /> - <entry key="outputName" value="ic_outline_star_border_24px" /> + <entry key="outputName" value="ic_outline_inbox_24px" /> <entry key="overrideSize" value="true" /> - <entry key="sourceFile" value="$USER_HOME$/Downloads/outline-star_border-24px.svg" /> + <entry key="sourceFile" value="$USER_HOME$/Downloads/outline-inbox-24px.svg" /> </map> </option> </PersistentState> diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser Binary files differindex 38cb74bb..4b09346a 100644 --- a/.idea/caches/build_file_checksums.ser +++ b/.idea/caches/build_file_checksums.ser diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser Binary files differindex 055c0fc8..e2b6be3f 100644 --- a/.idea/caches/gradle_models.ser +++ b/.idea/caches/gradle_models.ser diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 353588df..8e3f013e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,13 @@ android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - <activity android:name=".AccountPostsActivity" + <activity + android:name=".ViewMessageActivity" + android:label="@string/view_message_activity" + android:parentActivityName=".MainActivity" + android:theme="@style/AppTheme.NoActionBar" /> + <activity + android:name=".AccountPostsActivity" android:parentActivityName=".MainActivity" android:theme="@style/AppTheme.NoActionBar" /> <activity diff --git a/app/src/main/java/CustomView/CustomMarkwonView.java b/app/src/main/java/CustomView/CustomMarkwonView.java index d5828f13..940b6b76 100644 --- a/app/src/main/java/CustomView/CustomMarkwonView.java +++ b/app/src/main/java/CustomView/CustomMarkwonView.java @@ -6,11 +6,8 @@ import android.net.Uri; import android.util.AttributeSet; import androidx.annotation.Nullable; -import androidx.browser.customtabs.CustomTabsIntent; -import ml.docilealligator.infinityforreddit.R; -import ml.docilealligator.infinityforreddit.ViewSubredditDetailActivity; -import ml.docilealligator.infinityforreddit.ViewUserDetailActivity; +import ml.docilealligator.infinityforreddit.LinkResolverActivity; import ru.noties.markwon.SpannableConfiguration; import ru.noties.markwon.view.MarkwonView; @@ -26,30 +23,14 @@ public class CustomMarkwonView extends MarkwonView { public void setMarkdown(@Nullable String markdown, Context context) { SpannableConfiguration configuration = SpannableConfiguration.builder(context).linkResolver((view, link) -> { - if(link.startsWith("/u/") || link.startsWith("u/")) { - Intent intent = new Intent(context, ViewUserDetailActivity.class); - intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, link.substring(3)); - context.startActivity(intent); - } else if(link.startsWith("/r/") || link.startsWith("r/")) { - Intent intent = new Intent(context, ViewSubredditDetailActivity.class); - intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, link.substring(3)); - context.startActivity(intent); + Intent intent = new Intent(context, LinkResolverActivity.class); + Uri uri = Uri.parse(link); + if(uri.getScheme() == null && uri.getHost() == null) { + intent.setData(LinkResolverActivity.getRedditUriByPath(link)); } else { - CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); - // add share action to menu list - builder.addDefaultShareMenuItem(); - builder.setToolbarColor(context.getResources().getColor(R.color.colorPrimary)); - CustomTabsIntent customTabsIntent = builder.build(); - Uri uri = Uri.parse(link); - if(uri.getHost() != null && uri.getHost().contains("reddit.com")) { - customTabsIntent.intent.setPackage(context.getPackageName()); - } - String uriString = uri.toString(); - if(!uriString.startsWith("http://") && (!uriString.startsWith("https://"))) { - uriString = "http://" + uriString; - } - customTabsIntent.launchUrl(context, Uri.parse(uriString)); + intent.setData(uri); } + context.startActivity(intent); }).build(); super.setMarkdown(configuration, markdown); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java index 95e8f658..5a28dc40 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java @@ -35,4 +35,5 @@ interface AppComponent { void inject(EditCommentActivity editCommentActivity); void inject(AccountPostsActivity accountPostsActivity); void inject(PullNotificationWorker pullNotificationWorker); + void inject(ViewMessageActivity viewMessageActivity); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java index 89b2817b..347634aa 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentAndPostRecyclerViewAdapter.java @@ -72,7 +72,6 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie private String mSingleCommentId; private boolean mIsSingleCommentThreadMode; private CommentRecyclerViewAdapterCallback mCommentRecyclerViewAdapterCallback; - private LoadSubredditIconAsyncTask mLoadSubredditIconAsyncTask; private boolean isInitiallyLoading; private boolean isInitiallyLoadingFailed; private boolean mHasMoreComments; @@ -87,7 +86,6 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie RedditDataRoomDatabase redditDataRoomDatabase, RequestManager glide, String accessToken, String accountName, Post post, Locale locale, String singleCommentId, boolean isSingleCommentThreadMode, - LoadSubredditIconAsyncTask loadSubredditIconAsyncTask, CommentRecyclerViewAdapterCallback commentRecyclerViewAdapterCallback) { mActivity = activity; mRetrofit = retrofit; @@ -102,7 +100,6 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie mLocale = locale; mSingleCommentId = singleCommentId; mIsSingleCommentThreadMode = isSingleCommentThreadMode; - mLoadSubredditIconAsyncTask = loadSubredditIconAsyncTask; mCommentRecyclerViewAdapterCallback = commentRecyclerViewAdapterCallback; isInitiallyLoading = true; isInitiallyLoadingFailed = false; @@ -194,10 +191,10 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { if(holder.getItemViewType() == VIEW_TYPE_POST_DETAIL) { ((PostDetailViewHolder) holder).mTitleTextView.setText(mPost.getTitle()); - - if(mPost.getSubredditNamePrefixed().equals("u/" + mPost.getAuthor())) { + if(mPost.getSubredditNamePrefixed().startsWith("u/")) { if(mPost.getAuthorIconUrl() == null) { - new LoadUserDataAsyncTask(mRedditDataRoomDatabase.userDao(), mPost.getAuthor(), mOauthRetrofit, iconImageUrl -> { + String authorName = mPost.getAuthor().equals("[deleted]") ? mPost.getSubredditNamePrefixed().substring(2) : mPost.getAuthor(); + new LoadUserDataAsyncTask(mRedditDataRoomDatabase.userDao(), authorName, mOauthRetrofit, iconImageUrl -> { if(mActivity != null && getItemCount() > 0) { if(iconImageUrl == null || iconImageUrl.equals("")) { mGlide.load(R.drawable.subreddit_default_icon) @@ -229,10 +226,7 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie } } else { if(mPost.getSubredditIconUrl() == null) { - if(mLoadSubredditIconAsyncTask != null) { - mLoadSubredditIconAsyncTask.cancel(true); - } - mLoadSubredditIconAsyncTask = new LoadSubredditIconAsyncTask( + new LoadSubredditIconAsyncTask( mRedditDataRoomDatabase.subredditDao(), mPost.getSubredditNamePrefixed().substring(2), mRetrofit, iconImageUrl -> { if(iconImageUrl == null || iconImageUrl.equals("")) { @@ -248,9 +242,7 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie } mPost.setSubredditIconUrl(iconImageUrl); - }); - - mLoadSubredditIconAsyncTask.execute(); + }).execute(); } else if(!mPost.getSubredditIconUrl().equals("")) { mGlide.load(mPost.getSubredditIconUrl()) .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) @@ -713,10 +705,6 @@ class CommentAndPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie } void initiallyLoading() { - if(mLoadSubredditIconAsyncTask != null) { - mLoadSubredditIconAsyncTask.cancel(true); - } - if(mVisibleComments.size() != 0) { int previousSize = mVisibleComments.size(); mVisibleComments.clear(); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java index dd33af70..377cc07c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java @@ -87,7 +87,11 @@ public class CommentDataSource extends PageKeyedDataSource<String, CommentData> hasPostLiveData.postValue(true); } - callback.onResult(comments, null, after); + if(after == null || after.equals("") || after.equals("null")) { + callback.onResult(comments, null, null); + } else { + callback.onResult(comments, null, after); + } initialLoadStateLiveData.postValue(NetworkState.LOADED); } @@ -147,13 +151,15 @@ public class CommentDataSource extends PageKeyedDataSource<String, CommentData> } }).execute(); } else { - paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + Log.i("Comments fetch error", "Error fetching data"); + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error fetching data")); } } @Override public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) { - paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + Log.i("Comments fetch error", "Error fetchin data"); + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error fetching data")); } }); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java index c20920a9..06c4f232 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java @@ -57,13 +57,13 @@ class CommentsListingRecyclerViewAdapter extends PagedListAdapter<CommentData, R private static final DiffUtil.ItemCallback<CommentData> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentData>() { @Override - public boolean areItemsTheSame(@NonNull CommentData CommentData, @NonNull CommentData t1) { - return CommentData.getId().equals(t1.getId()); + public boolean areItemsTheSame(@NonNull CommentData commentData, @NonNull CommentData t1) { + return commentData.getId().equals(t1.getId()); } @Override - public boolean areContentsTheSame(@NonNull CommentData CommentData, @NonNull CommentData t1) { - return CommentData.getCommentContent().equals(t1.getCommentContent()); + public boolean areContentsTheSame(@NonNull CommentData commentData, @NonNull CommentData t1) { + return commentData.getCommentContent().equals(t1.getCommentContent()); } }; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java index 04d6f8de..424c1c83 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchMessages.java @@ -22,8 +22,8 @@ import retrofit2.Retrofit; class FetchMessages { interface FetchMessagesListener { - void fetchSuccess(@Nullable ArrayList<Message> messages); - void fetchFailed(boolean shouldRetry); + void fetchSuccess(ArrayList<Message> messages, @Nullable String after); + void fetchFailed(); } static final String WHERE_INBOX = "inbox"; @@ -32,22 +32,21 @@ class FetchMessages { 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) + String after, FetchMessagesListener fetchMessagesListener) { + oauthRetrofit.create(RedditAPI.class).getMessages(RedditUtils.getOAuthHeader(accessToken), where, after) .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(); + new ParseMessageAsnycTask(response.body(), locale, fetchMessagesListener::fetchSuccess).execute(); } else { - fetchMessagesListener.fetchFailed(true); + fetchMessagesListener.fetchFailed(); } } @Override public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) { - fetchMessagesListener.fetchFailed(true); + fetchMessagesListener.fetchFailed(); } }); } @@ -104,12 +103,13 @@ class FetchMessages { private static class ParseMessageAsnycTask extends AsyncTask<Void, Void, Void> { interface ParseMessageAsyncTaskListener { - void parseSuccess(ArrayList<Message> messages); + void parseSuccess(ArrayList<Message> messages, @Nullable String after); } private String response; private Locale locale; private ArrayList<Message> messages; + private String after; private ParseMessageAsyncTaskListener parseMessageAsyncTaskListener; ParseMessageAsnycTask(String response, Locale locale, ParseMessageAsyncTaskListener parseMessageAsnycTaskListener) { @@ -122,13 +122,18 @@ class FetchMessages { @Override protected Void doInBackground(Void... voids) { messages = parseMessage(response, locale); + try { + 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); + parseMessageAsyncTaskListener.parseSuccess(messages, after); } } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java index d95a210e..1c6ee055 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/LinkResolverActivity.java @@ -85,11 +85,10 @@ public class LinkResolverActivity extends AppCompatActivity { builder.setToolbarColor(getResources().getColor(R.color.colorPrimary)); CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.intent.setPackage(resolveInfos.get(0).activityInfo.packageName); - String uriString = uri.toString(); - if(!uriString.startsWith("http://") || (!uriString.startsWith("https://"))) { - uriString = "http://" + uriString; + if(uri.getScheme() == null) { + uri = Uri.parse("http://" + uri.toString()); } - customTabsIntent.launchUrl(this, Uri.parse(uriString)); + customTabsIntent.launchUrl(this, uri); } else { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(uri); @@ -133,7 +132,7 @@ public class LinkResolverActivity extends AppCompatActivity { return packagesSupportingCustomTabs; } - static Uri getRedditUriByPath(String path) { + public 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 b9d79b94..f2c58214 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java @@ -83,6 +83,7 @@ public class MainActivity extends AppCompatActivity implements SortTypeBottomShe @BindView(R.id.all_drawer_items_linear_layout_main_activity) LinearLayout allDrawerItemsLinearLayout; @BindView(R.id.profile_linear_layout_main_activity) LinearLayout profileLinearLayout; @BindView(R.id.subscriptions_linear_layout_main_activity) LinearLayout subscriptionLinearLayout; + @BindView(R.id.inbox_linear_layout_main_activity) LinearLayout inboxLinearLayout; @BindView(R.id.upvoted_linear_layout_main_activity) LinearLayout upvotedLinearLayout; @BindView(R.id.downvoted_linear_layout_main_activity) LinearLayout downvotedLinearLayout; @BindView(R.id.hidden_linear_layout_main_activity) LinearLayout hiddenLinearLayout; @@ -385,6 +386,12 @@ public class MainActivity extends AppCompatActivity implements SortTypeBottomShe mAccountNameTextView.setText(R.string.anonymous_account); profileLinearLayout.setVisibility(View.GONE); subscriptionLinearLayout.setVisibility(View.GONE); + inboxLinearLayout.setVisibility(View.GONE); + upvotedLinearLayout.setVisibility(View.GONE); + downvotedLinearLayout.setVisibility(View.GONE); + hiddenLinearLayout.setVisibility(View.GONE); + savedLinearLayout.setVisibility(View.GONE); + gildedLinearLayout.setVisibility(View.GONE); divider.setVisibility(View.GONE); } @@ -417,6 +424,12 @@ public class MainActivity extends AppCompatActivity implements SortTypeBottomShe drawer.closeDrawers(); }); + inboxLinearLayout.setOnClickListener(view -> { + Intent intent = new Intent(this, ViewMessageActivity.class); + startActivity(intent); + drawer.closeDrawers(); + }); + upvotedLinearLayout.setOnClickListener(view -> { Intent intent = new Intent(MainActivity.this, AccountPostsActivity.class); intent.putExtra(AccountPostsActivity.EXTRA_USER_WHERE, PostDataSource.USER_WHERE_UPVOTED); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java index 1846a8ef..1df3b233 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Message.java @@ -104,7 +104,7 @@ class Message { return formattedTime; } - public boolean isWasComment() { + public boolean wasComment() { return wasComment; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSource.java new file mode 100644 index 00000000..3b2af4fb --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSource.java @@ -0,0 +1,123 @@ +package ml.docilealligator.infinityforreddit; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.PageKeyedDataSource; + +import java.util.ArrayList; +import java.util.Locale; + +import retrofit2.Retrofit; + +class MessageDataSource extends PageKeyedDataSource<String, Message> { + private Retrofit oauthRetrofit; + private Locale locale; + private String accessToken; + private String where; + + private MutableLiveData<NetworkState> paginationNetworkStateLiveData; + private MutableLiveData<NetworkState> initialLoadStateLiveData; + private MutableLiveData<Boolean> hasPostLiveData; + + private LoadInitialParams<String> initialParams; + private LoadInitialCallback<String, Message> initialCallback; + private LoadParams<String> params; + private LoadCallback<String, Message> callback; + + MessageDataSource(Retrofit oauthRetrofit, Locale locale, String accessToken, String where) { + this.oauthRetrofit = oauthRetrofit; + this.locale = locale; + this.accessToken = accessToken; + this.where = where; + paginationNetworkStateLiveData = new MutableLiveData<>(); + initialLoadStateLiveData = new MutableLiveData<>(); + hasPostLiveData = new MutableLiveData<>(); + } + + MutableLiveData<NetworkState> getPaginationNetworkStateLiveData() { + return paginationNetworkStateLiveData; + } + + MutableLiveData<NetworkState> getInitialLoadStateLiveData() { + return initialLoadStateLiveData; + } + + MutableLiveData<Boolean> hasPostLiveData() { + return hasPostLiveData; + } + + void retry() { + loadInitial(initialParams, initialCallback); + } + + void retryLoadingMore() { + loadAfter(params, callback); + } + + @Override + public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, Message> callback) { + initialParams = params; + initialCallback = callback; + + initialLoadStateLiveData.postValue(NetworkState.LOADING); + + FetchMessages.fetchMessagesAsync(oauthRetrofit, locale, accessToken, where, null, new FetchMessages.FetchMessagesListener() { + @Override + public void fetchSuccess(ArrayList<Message> messages, @Nullable String after) { + if(messages.size() == 0) { + hasPostLiveData.postValue(false); + } else { + hasPostLiveData.postValue(true); + } + + if(after == null || after.equals("") || after.equals("null")) { + callback.onResult(messages, null, null); + } else { + callback.onResult(messages, null, after); + } + initialLoadStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void fetchFailed() { + Log.i("Messages fetch error", "Error fetch messages"); + initialLoadStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error fetch messages")); + } + }); + } + + @Override + public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, Message> callback) { + + } + + @Override + public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, Message> callback) { + this.params = params; + this.callback = callback; + + paginationNetworkStateLiveData.postValue(NetworkState.LOADING); + + FetchMessages.fetchMessagesAsync(oauthRetrofit, locale, accessToken, where, params.key, new FetchMessages.FetchMessagesListener() { + @Override + public void fetchSuccess(ArrayList<Message> messages, @Nullable String after) { + if(after == null || after.equals("") || after.equals("null")) { + callback.onResult(messages, null); + } else { + callback.onResult(messages, after); + } + + paginationNetworkStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void fetchFailed() { + Log.i("Comments fetch error", "Error parsing data"); + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error fetching data")); + } + }); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSourceFactory.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSourceFactory.java new file mode 100644 index 00000000..b1470bf7 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageDataSourceFactory.java @@ -0,0 +1,47 @@ +package ml.docilealligator.infinityforreddit; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.DataSource; + +import java.util.Locale; + +import retrofit2.Retrofit; + +class MessageDataSourceFactory extends DataSource.Factory { + private Retrofit oauthRetrofit; + private Locale locale; + private String accessToken; + private String where; + + private MessageDataSource messageDataSource; + private MutableLiveData<MessageDataSource> messageDataSourceLiveData; + + MessageDataSourceFactory(Retrofit oauthRetrofit, Locale locale, String accessToken, String where) { + this.oauthRetrofit = oauthRetrofit; + this.locale = locale; + this.accessToken = accessToken; + this.where = where; + messageDataSourceLiveData = new MutableLiveData<>(); + } + + @NonNull + @Override + public DataSource create() { + messageDataSource = new MessageDataSource(oauthRetrofit, locale, accessToken, where); + messageDataSourceLiveData.postValue(messageDataSource); + return messageDataSource; + } + + public MutableLiveData<MessageDataSource> getMessageDataSourceLiveData() { + return messageDataSourceLiveData; + } + + MessageDataSource getMessageDataSource() { + return messageDataSource; + } + + void changeWhere(String where) { + this.where = where; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MessageRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageRecyclerViewAdapter.java new file mode 100644 index 00000000..3865123b --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageRecyclerViewAdapter.java @@ -0,0 +1,195 @@ +package ml.docilealligator.infinityforreddit; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import CustomView.CustomMarkwonView; +import butterknife.BindView; +import butterknife.ButterKnife; + +class MessageRecyclerViewAdapter extends PagedListAdapter<Message, RecyclerView.ViewHolder> { + private static final int VIEW_TYPE_DATA = 0; + private static final int VIEW_TYPE_ERROR = 1; + private static final int VIEW_TYPE_LOADING = 2; + + private Context mContext; + private Resources resources; + + private NetworkState networkState; + private CommentsListingRecyclerViewAdapter.RetryLoadingMoreCallback mRetryLoadingMoreCallback; + + interface RetryLoadingMoreCallback { + void retryLoadingMore(); + } + + MessageRecyclerViewAdapter(Context context) { + super(DIFF_CALLBACK); + mContext = context; + resources = context.getResources(); + } + + private static final DiffUtil.ItemCallback<Message> DIFF_CALLBACK = new DiffUtil.ItemCallback<Message>() { + @Override + public boolean areItemsTheSame(@NonNull Message message, @NonNull Message t1) { + return message.getId().equals(t1.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull Message message, @NonNull Message t1) { + return message.getBody().equals(t1.getBody()); + } + }; + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_DATA) { + return new DataViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_message, parent, false)); + } else if(viewType == VIEW_TYPE_ERROR) { + return new ErrorViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_error, parent, false)); + } else { + return new LoadingViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_loading, parent, false)); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if(holder instanceof DataViewHolder) { + Message message = getItem(holder.getAdapterPosition()); + if(message != null) { + if(message.isNew()) { + ((DataViewHolder) holder).itemView.setBackgroundColor( + resources.getColor(R.color.unreadMessageBackgroundColor)); + } + + if(message.wasComment()) { + ((DataViewHolder) holder).authorTextView.setTextColor(resources.getColor(R.color.colorPrimaryDarkDayNightTheme)); + ((DataViewHolder) holder).titleTextView.setText(message.getTitle()); + } else { + ((DataViewHolder) holder).titleTextView.setVisibility(View.GONE); + } + + ((DataViewHolder) holder).authorTextView.setText(message.getAuthor()); + String subject = message.getSubject().substring(0, 1).toUpperCase() + message.getSubject().substring(1); + ((DataViewHolder) holder).subjectTextView.setText(subject); + ((DataViewHolder) holder).contentCustomMarkwonView.setMarkdown(message.getBody(), mContext); + + ((DataViewHolder) holder).itemView.setOnClickListener(view -> { + if(message.getContext() != null && !message.getContext().equals("")) { + Uri uri = LinkResolverActivity.getRedditUriByPath(message.getContext()); + Intent intent = new Intent(mContext, LinkResolverActivity.class); + intent.setData(uri); + mContext.startActivity(intent); + } + }); + + ((DataViewHolder) holder).authorTextView.setOnClickListener(view -> { + if(message.wasComment()) { + Intent intent = new Intent(mContext, ViewUserDetailActivity.class); + intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getAuthor()); + mContext.startActivity(intent); + } + }); + + ((DataViewHolder) holder).contentCustomMarkwonView.setOnClickListener(view -> ((DataViewHolder) holder).itemView.performClick()); + } + } + } + + @Override + public int getItemViewType(int position) { + // Reached at the end + if (hasExtraRow() && position == getItemCount() - 1) { + if (networkState.getStatus() == NetworkState.Status.LOADING) { + return VIEW_TYPE_LOADING; + } else { + return VIEW_TYPE_ERROR; + } + } else { + return VIEW_TYPE_DATA; + } + } + + @Override + public int getItemCount() { + if(hasExtraRow()) { + return super.getItemCount() + 1; + } + return super.getItemCount(); + } + + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + super.onViewRecycled(holder); + if(holder instanceof DataViewHolder) { + ((DataViewHolder) holder).itemView.setBackgroundColor(resources.getColor(android.R.color.white)); + ((DataViewHolder) holder).titleTextView.setVisibility(View.VISIBLE); + ((DataViewHolder) holder).authorTextView.setTextColor(resources.getColor(R.color.primaryTextColor)); + } + } + + private boolean hasExtraRow() { + return networkState != null && networkState.getStatus() != NetworkState.Status.SUCCESS; + } + + void setNetworkState(NetworkState newNetworkState) { + NetworkState previousState = this.networkState; + boolean previousExtraRow = hasExtraRow(); + this.networkState = newNetworkState; + boolean newExtraRow = hasExtraRow(); + if (previousExtraRow != newExtraRow) { + if (previousExtraRow) { + notifyItemRemoved(super.getItemCount()); + } else { + notifyItemInserted(super.getItemCount()); + } + } else if (newExtraRow && !previousState.equals(newNetworkState)) { + notifyItemChanged(getItemCount() - 1); + } + } + + class DataViewHolder extends RecyclerView.ViewHolder { + View itemView; + @BindView(R.id.author_text_view_item_message) TextView authorTextView; + @BindView(R.id.subject_text_view_item_message) TextView subjectTextView; + @BindView(R.id.title_text_view_item_message) TextView titleTextView; + @BindView(R.id.content_custom_markwon_view_item_message) CustomMarkwonView contentCustomMarkwonView; + + DataViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + this.itemView = itemView; + } + } + + class ErrorViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.error_text_view_item_footer_error) TextView errorTextView; + @BindView(R.id.retry_button_item_footer_error) Button retryButton; + + ErrorViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + errorTextView.setText(R.string.load_comments_failed); + retryButton.setOnClickListener(view -> mRetryLoadingMoreCallback.retryLoadingMore()); + } + } + + class LoadingViewHolder extends RecyclerView.ViewHolder { + LoadingViewHolder(@NonNull View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MessageViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageViewModel.java new file mode 100644 index 00000000..4ad155d6 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MessageViewModel.java @@ -0,0 +1,100 @@ +package ml.docilealligator.infinityforreddit; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PagedList; + +import java.util.Locale; + +import retrofit2.Retrofit; + +public class MessageViewModel extends ViewModel { + private MessageDataSourceFactory messageDataSourceFactory; + private LiveData<NetworkState> paginationNetworkState; + private LiveData<NetworkState> initialLoadingState; + private LiveData<Boolean> hasMessageLiveData; + private LiveData<PagedList<Message>> messages; + private MutableLiveData<String> whereLiveData; + + public MessageViewModel(Retrofit retrofit, Locale locale, String accessToken, String where) { + messageDataSourceFactory = new MessageDataSourceFactory(retrofit, locale, accessToken, where); + + initialLoadingState = Transformations.switchMap(messageDataSourceFactory.getMessageDataSourceLiveData(), + MessageDataSource::getInitialLoadStateLiveData); + paginationNetworkState = Transformations.switchMap(messageDataSourceFactory.getMessageDataSourceLiveData(), + MessageDataSource::getPaginationNetworkStateLiveData); + hasMessageLiveData = Transformations.switchMap(messageDataSourceFactory.getMessageDataSourceLiveData(), + MessageDataSource::hasPostLiveData); + + whereLiveData = new MutableLiveData<>(); + whereLiveData.postValue(where); + + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(25) + .build(); + + messages = Transformations.switchMap(whereLiveData, newWhere -> { + messageDataSourceFactory.changeWhere(whereLiveData.getValue()); + return (new LivePagedListBuilder(messageDataSourceFactory, pagedListConfig)).build(); + }); + } + + LiveData<PagedList<Message>> getMessages() { + return messages; + } + + LiveData<NetworkState> getPaginationNetworkState() { + return paginationNetworkState; + } + + LiveData<NetworkState> getInitialLoadingState() { + return initialLoadingState; + } + + LiveData<Boolean> hasMessage() { + return hasMessageLiveData; + } + + void refresh() { + messageDataSourceFactory.getMessageDataSource().invalidate(); + } + + void retry() { + messageDataSourceFactory.getMessageDataSource().retry(); + } + + void retryLoadingMore() { + messageDataSourceFactory.getMessageDataSource().retryLoadingMore(); + } + + void changeWhere(String where) { + whereLiveData.postValue(where); + } + + public static class Factory extends ViewModelProvider.NewInstanceFactory { + private Retrofit retrofit; + private Locale locale; + private String accessToken; + private String where; + + public Factory(Retrofit retrofit, Locale locale, String accessToken, String where) { + this.retrofit = retrofit; + this.locale = locale; + this.accessToken = accessToken; + this.where = where; + } + + @NonNull + @Override + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + return (T) new MessageViewModel(retrofit, locale, accessToken, where); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java index 22e3787c..56d35248 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java @@ -43,7 +43,7 @@ class ParseSubredditData { jsonResponse = new JSONObject(response); parseFailed = false; } catch (JSONException e) { - Log.i("subreddit json error", e.getMessage()); + Log.i("subreddit json error", "message: " + e.getMessage()); parseSubredditDataListener.onParseSubredditDataFail(); } } @@ -54,38 +54,9 @@ class ParseSubredditData { JSONObject data = jsonResponse.getJSONObject(JSONUtils.DATA_KEY); mNCurrentOnlineSubscribers = data.getInt(JSONUtils.ACTIVE_USER_COUNT_KEY); subredditData = parseSubredditData(data); - /*String id = data.getString(JSONUtils.EXTRA_NAME); - String subredditFullName = data.getString(JSONUtils.DISPLAY_NAME); - String description = data.getString(JSONUtils.PUBLIC_DESCRIPTION_KEY).trim(); - - String bannerImageUrl; - if(data.isNull(JSONUtils.BANNER_BACKGROUND_IMAGE_KEY)) { - bannerImageUrl = ""; - } else { - bannerImageUrl = data.getString(JSONUtils.BANNER_BACKGROUND_IMAGE_KEY); - } - if(bannerImageUrl.equals("") && !data.isNull(JSONUtils.BANNER_IMG_KEY)) { - bannerImageUrl= data.getString(JSONUtils.BANNER_IMG_KEY); - } - - String iconUrl; - if(data.isNull(JSONUtils.COMMUNITY_ICON_KEY)) { - iconUrl = ""; - } else { - iconUrl = data.getString(JSONUtils.COMMUNITY_ICON_KEY); - } - if(iconUrl.equals("") && !data.isNull(JSONUtils.ICON_IMG_KEY)) { - iconUrl = data.getString(JSONUtils.ICON_IMG_KEY); - } - - int nSubscribers = data.getInt(JSONUtils.SUBSCRIBERS_KEY); - int nCurrentOnlineSubscribers = data.getInt(JSONUtils.ACTIVE_USER_COUNT_KEY); - subredditData = new SubredditData(id, subredditFullName, iconUrl, bannerImageUrl, description, nSubscribers); - mNCurrentOnlineSubscribers = nCurrentOnlineSubscribers;*/ } catch (JSONException e) { parseFailed = true; Log.i("parse", "SubredditData error"); - parseSubredditDataListener.onParseSubredditDataFail(); } return null; } @@ -114,7 +85,7 @@ class ParseSubredditData { parseFailed = false; subredditListingData = new ArrayList<>(); } catch (JSONException e) { - Log.i("subreddit json error", e.getMessage()); + Log.i("subreddit json error", "message: " + e.getMessage()); parseFailed = true; } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java index 239bf8c0..8d00a010 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java @@ -248,7 +248,8 @@ class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView.ViewHo }); } else { if(post.getAuthorIconUrl() == null) { - new LoadUserDataAsyncTask(mUserDao, post.getAuthor(), mRetrofit, iconImageUrl -> { + String authorName = post.getAuthor().equals("[deleted]") ? post.getSubredditNamePrefixed().substring(2) : post.getAuthor(); + new LoadUserDataAsyncTask(mUserDao, authorName, mRetrofit, iconImageUrl -> { if(mContext != null && getItemCount() > 0) { if(iconImageUrl == null || iconImageUrl.equals("")) { mGlide.load(R.drawable.subreddit_default_icon) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java index 512096c7..57c13212 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PullNotificationWorker.java @@ -49,13 +49,13 @@ public class PullNotificationWorker extends Worker { Account currentAccount = redditDataRoomDatabase.accountDao().getCurrentAccount(); Response<String> response = mOauthRetrofit.create(RedditAPI.class).getMessages( RedditUtils.getOAuthHeader(currentAccount.getAccessToken()), - FetchMessages.WHERE_COMMENTS).execute(); + FetchMessages.WHERE_UNREAD, null).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) { + if(messages != null && !messages.isEmpty()) { NotificationManagerCompat notificationManager = NotificationUtils.getNotificationManager(context); NotificationCompat.Builder summaryBuilder = NotificationUtils.buildSummaryNotification(context, diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java index ddd53c47..a187b521 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java @@ -158,5 +158,5 @@ public interface RedditAPI { 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); + Call<String> getMessages(@HeaderMap Map<String, String> headers, @Path("where") String where, @Query("after") String after); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewMessageActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewMessageActivity.java new file mode 100644 index 00000000..b61438f4 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewMessageActivity.java @@ -0,0 +1,230 @@ +package ml.docilealligator.infinityforreddit; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar; + +import javax.inject.Inject; +import javax.inject.Named; + +import Account.Account; +import butterknife.BindView; +import butterknife.ButterKnife; +import retrofit2.Retrofit; + +public class ViewMessageActivity extends AppCompatActivity { + + private static final String NULL_ACCESS_TOKEN_STATE = "NATS"; + private static final String ACCESS_TOKEN_STATE = "ATS"; + + @BindView(R.id.collapsing_toolbar_layout_view_message_activity) CollapsingToolbarLayout collapsingToolbarLayout; + @BindView(R.id.appbar_layout_view_message_activity) AppBarLayout appBarLayout; + @BindView(R.id.toolbar_view_message_activity) Toolbar toolbar; + @BindView(R.id.progress_bar_view_message_activity) CircleProgressBar mProgressBar; + @BindView(R.id.recycler_view_view_message_activity) RecyclerView recyclerView; + @BindView(R.id.fetch_messages_info_linear_layout_view_message_activity) LinearLayout mFetchMessageInfoLinearLayout; + @BindView(R.id.fetch_messages_info_image_view_view_message_activity) ImageView mFetchMessageInfoImageView; + @BindView(R.id.fetch_messages_info_text_view_view_message_activity) TextView mFetchMessageInfoTextView; + + private boolean mNullAccessToken = false; + private String mAccessToken; + + private MessageRecyclerViewAdapter mAdapter; + + private RequestManager mGlide; + + MessageViewModel mMessageViewModel; + + @Inject + @Named("oauth") + Retrofit mOauthRetrofit; + + @Inject + RedditDataRoomDatabase mRedditDataRoomDatabase; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_view_message); + + ButterKnife.bind(this); + + ((Infinity) getApplication()).getAppComponent().inject(this); + + mGlide = Glide.with(this); + + Resources resources = getResources(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 + && (resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT + || resources.getBoolean(R.bool.isTablet))) { + Window window = getWindow(); + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + + boolean lightNavBar = false; + if((resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) != Configuration.UI_MODE_NIGHT_YES) { + lightNavBar = true; + } + boolean finalLightNavBar = lightNavBar; + + View decorView = window.getDecorView(); + if(finalLightNavBar) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + } + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + void onStateChanged(AppBarLayout appBarLayout, State state) { + if (state == State.COLLAPSED) { + if(finalLightNavBar) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + } + } else if (state == State.EXPANDED) { + if(finalLightNavBar) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + } + } + } + }); + + int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (statusBarResourceId > 0) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); + params.topMargin = getResources().getDimensionPixelSize(statusBarResourceId); + toolbar.setLayoutParams(params); + } + + int navBarResourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + if (navBarResourceId > 0) { + recyclerView.setPadding(0, 0, 0, resources.getDimensionPixelSize(navBarResourceId)); + } + } + + toolbar.setTitle(R.string.inbox); + setSupportActionBar(toolbar); + + if(savedInstanceState != null) { + mNullAccessToken = savedInstanceState.getBoolean(NULL_ACCESS_TOKEN_STATE); + mAccessToken = savedInstanceState.getString(ACCESS_TOKEN_STATE); + + if(!mNullAccessToken && mAccessToken == null) { + getCurrentAccountAndFetchMessage(); + } else { + bindView(); + } + } else { + getCurrentAccountAndFetchMessage(); + } + } + + private void getCurrentAccountAndFetchMessage() { + new GetCurrentAccountAsyncTask(mRedditDataRoomDatabase.accountDao(), new GetCurrentAccountAsyncTask.GetCurrentAccountAsyncTaskListener() { + @Override + public void success(Account account) { + if(account == null) { + mNullAccessToken = true; + } else { + mAccessToken = account.getAccessToken(); + + bindView(); + } + } + }).execute(); + } + + private void bindView() { + mAdapter = new MessageRecyclerViewAdapter(this); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(mAdapter); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, layoutManager.getOrientation()); + recyclerView.addItemDecoration(dividerItemDecoration); + + MessageViewModel.Factory factory = new MessageViewModel.Factory(mOauthRetrofit, + getResources().getConfiguration().locale, mAccessToken, FetchMessages.WHERE_INBOX); + mMessageViewModel = new ViewModelProvider(this, factory).get(MessageViewModel.class); + mMessageViewModel.getMessages().observe(this, messages -> mAdapter.submitList(messages)); + + mMessageViewModel.getInitialLoadingState().observe(this, networkState -> { + if(networkState.getStatus().equals(NetworkState.Status.SUCCESS)) { + mProgressBar.setVisibility(View.GONE); + } else if(networkState.getStatus().equals(NetworkState.Status.FAILED)) { + mFetchMessageInfoLinearLayout.setOnClickListener(view -> mMessageViewModel.retry()); + showErrorView(R.string.load_messages_failed); + } else { + mFetchMessageInfoLinearLayout.setVisibility(View.GONE); + mProgressBar.setVisibility(View.VISIBLE); + } + }); + + mMessageViewModel.hasMessage().observe(this, hasMessage -> { + if(hasMessage) { + mFetchMessageInfoLinearLayout.setVisibility(View.GONE); + } else { + mFetchMessageInfoLinearLayout.setOnClickListener(view -> { + //Do nothing + }); + showErrorView(R.string.no_messages); + } + }); + + mMessageViewModel.getPaginationNetworkState().observe(this, networkState -> { + mAdapter.setNetworkState(networkState); + }); + } + + private void showErrorView(int stringResId) { + mProgressBar.setVisibility(View.GONE); + mFetchMessageInfoLinearLayout.setVisibility(View.VISIBLE); + mFetchMessageInfoTextView.setText(stringResId); + mGlide.load(R.drawable.load_post_error_indicator).into(mFetchMessageInfoImageView); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.view_message_activity, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if(item.getItemId() == R.id.action_refresh_view_message_activity) { + mMessageViewModel.refresh(); + return true; + } else if(item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return false; + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(NULL_ACCESS_TOKEN_STATE, mNullAccessToken); + outState.putString(ACCESS_TOKEN_STATE, mAccessToken); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java index 1039fc96..9e7bdb9b 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java @@ -101,7 +101,6 @@ public class ViewPostDetailActivity extends AppCompatActivity implements FlairBo private LinearLayoutManager mLinearLayoutManager; private CommentAndPostRecyclerViewAdapter mAdapter; - private LoadSubredditIconAsyncTask mLoadSubredditIconAsyncTask; @BindView(R.id.coordinator_layout_view_post_detail) CoordinatorLayout mCoordinatorLayout; @BindView(R.id.appbar_layout_view_post_detail_activity) AppBarLayout appBarLayout; @@ -255,7 +254,7 @@ public class ViewPostDetailActivity extends AppCompatActivity implements FlairBo } mAdapter = new CommentAndPostRecyclerViewAdapter(ViewPostDetailActivity.this, mRetrofit, mOauthRetrofit, mRedditDataRoomDatabase, mGlide, mAccessToken, mAccountName, mPost, - mLocale, mSingleCommentId, isSingleCommentThreadMode, mLoadSubredditIconAsyncTask, + mLocale, mSingleCommentId, isSingleCommentThreadMode, new CommentAndPostRecyclerViewAdapter.CommentRecyclerViewAdapterCallback() { @Override public void updatePost(Post post) { @@ -331,7 +330,7 @@ public class ViewPostDetailActivity extends AppCompatActivity implements FlairBo mAdapter = new CommentAndPostRecyclerViewAdapter(ViewPostDetailActivity.this, mRetrofit, mOauthRetrofit, mRedditDataRoomDatabase, mGlide, mAccessToken, mAccountName, mPost, - mLocale, mSingleCommentId, isSingleCommentThreadMode, mLoadSubredditIconAsyncTask, + mLocale, mSingleCommentId, isSingleCommentThreadMode, new CommentAndPostRecyclerViewAdapter.CommentRecyclerViewAdapterCallback() { @Override public void updatePost(Post post) { @@ -817,9 +816,6 @@ public class ViewPostDetailActivity extends AppCompatActivity implements FlairBo EventBus.getDefault().unregister(this); super.onDestroy(); Bridge.clear(this); - if(mLoadSubredditIconAsyncTask != null) { - mLoadSubredditIconAsyncTask.cancel(true); - } } @Override diff --git a/app/src/main/res/drawable/ic_outline_inbox_24px.xml b/app/src/main/res/drawable/ic_outline_inbox_24px.xml new file mode 100644 index 00000000..97d34185 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_inbox_24px.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19v-3h3.56c0.69,1.19 1.97,2 3.45,2s2.75,-0.81 3.45,-2L19,16v3zM19,14h-4.99c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2L5,14L5,5h14v9z"/> +</vector> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 407c2a1d..849253a1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -91,6 +91,32 @@ </LinearLayout> <LinearLayout + android:id="@+id/inbox_linear_layout_main_activity" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:focusable="true" + android:padding="16dp"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" + android:layout_marginEnd="32dp" + android:src="@drawable/ic_outline_inbox_24px" + android:tint="@color/primaryTextColor"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/inbox" + android:textColor="@color/primaryTextColor" /> + + </LinearLayout> + + <LinearLayout android:id="@+id/upvoted_linear_layout_main_activity" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/activity_view_message.xml b/app/src/main/res/layout/activity_view_message.xml new file mode 100644 index 00000000..b05badf3 --- /dev/null +++ b/app/src/main/res/layout/activity_view_message.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ViewMessageActivity"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appbar_layout_view_message_activity" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> + + <com.google.android.material.appbar.CollapsingToolbarLayout + android:id="@+id/collapsing_toolbar_layout_view_message_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_scrollFlags="scroll|enterAlways" + app:titleEnabled="false" + app:toolbarId="@+id/toolbar_view_message_activity"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar_view_message_activity" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + app:popupTheme="@style/AppTheme.PopupOverlay" + app:navigationIcon="?attr/homeAsUpIndicator" /> + + </com.google.android.material.appbar.CollapsingToolbarLayout> + + </com.google.android.material.appbar.AppBarLayout> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" > + + <com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar + android:id="@+id/progress_bar_view_message_activity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + app:mlpb_progress_stoke_width="3dp" + app:mlpb_progress_color="@color/colorAccent" + app:mlpb_background_color="@color/circularProgressBarBackground" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler_view_view_message_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false"/> + + <LinearLayout + android:id="@+id/fetch_messages_info_linear_layout_view_message_activity" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:gravity="center" + android:orientation="vertical" + android:visibility="gone"> + + <ImageView + android:id="@+id/fetch_messages_info_image_view_view_message_activity" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/fetch_messages_info_text_view_view_message_activity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:gravity="center" /> + + </LinearLayout> + + </RelativeLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml new file mode 100644 index 00000000..12fd25c4 --- /dev/null +++ b/app/src/main/res/layout/item_message.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp"> + + <TextView + android:id="@+id/author_text_view_item_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:textColor="@color/primaryTextColor" /> + + <TextView + android:id="@+id/subject_text_view_item_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:textSize="16sp" + android:textColor="@color/primaryTextColor" /> + + <TextView + android:id="@+id/title_text_view_item_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:textColor="@color/primaryTextColor" /> + + <CustomView.CustomMarkwonView + android:id="@+id/content_custom_markwon_view_item_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/menu/view_message_activity.xml b/app/src/main/res/menu/view_message_activity.xml new file mode 100644 index 00000000..ae1bdfdc --- /dev/null +++ b/app/src/main/res/menu/view_message_activity.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/action_refresh_view_message_activity" + android:orderInCategory="1" + android:title="@string/action_refresh" + app:showAsAction="never" /> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ee3bdeed..ef901a9e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -47,4 +47,6 @@ <color name="cardViewBackgroundColor">#FFFFFF</color> <color name="singleCommentThreadBackgroundColor">#B3E5F9</color> + + <color name="unreadMessageBackgroundColor">#B3E5F9</color> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 359315a2..1943b769 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ <string name="search_subreddits_activity_label">Subreddits</string> <string name="edit_post_activity_label">Edit Post</string> <string name="edit_comment_activity_label">Edit Comment</string> + <string name="view_message_activity">Inbox</string> <string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string> @@ -50,19 +51,23 @@ <string name="no_users">No users found</string> <string name="no_storage_permission">No storage permission to save this file</string> - <string name="load_comments_failed">Error loading comments.</string> + <string name="load_comments_failed">Error loading comments</string> <string name="retry">Retry</string> <string name="comments">Comments</string> <string name="no_comments_yet">No comments yet. Write a comment?</string> <string name="vote_failed">Vote failed</string> <string name="refresh_post_failed">Error refreshing the post</string> + <string name="load_messages_failed">Error loading messages</string> + <string name="no_messages">Empty</string> + <string name="nsfw">NSFW</string> <string name="karma_info">Karma: %1$d</string> <string name="profile">Profile</string> <string name="following">Following</string> <string name="subscriptions">Subscriptions</string> + <string name="inbox">Inbox</string> <string name="upvoted">Upvoted</string> <string name="downvoted">Downvoted</string> <string name="hidden">Hidden</string> |