diff options
author | Alex Ning <chineseperson5@gmail.com> | 2019-08-18 08:03:58 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2019-08-18 08:03:58 +0000 |
commit | ffd1d6e204fc7032a13216fe0c6dbe78205b2a98 (patch) | |
tree | 31d8f2d48a772968d37dc20df0b91359fbe50bf7 | |
parent | 1984332fddeebe02db8e569f821391a45f97d904 (diff) | |
download | infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar.gz infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar.bz2 infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar.lz infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar.xz infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.tar.zst infinity-for-reddit-ffd1d6e204fc7032a13216fe0c6dbe78205b2a98.zip |
View messages in ViewMessageActivity. Use LinkResolverActivity to handle link clicking in CustomMarkwonView. Fixed cannot load user profile image after the post is deleted.
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> |