From bde545b75fa4beb140a6ce10861d178505a16bdc Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Tue, 30 Jun 2020 13:33:05 +0800 Subject: [PATCH] Implemente a chat UI for private messages. --- .../Activity/ViewPrivateMessagesActivity.java | 51 ++++- ...vateMessagesDetailRecyclerViewAdapter.java | 188 +++++++++++++----- .../private_message_ballon_received.xml | 11 + .../drawable/private_message_ballon_sent.xml | 11 + .../layout/activity_view_private_messages.xml | 8 - .../layout/item_private_message_received.xml | 61 ++++++ .../res/layout/item_private_message_sent.xml | 43 ++++ 7 files changed, 307 insertions(+), 66 deletions(-) create mode 100644 app/src/main/res/drawable/private_message_ballon_received.xml create mode 100644 app/src/main/res/drawable/private_message_ballon_sent.xml create mode 100644 app/src/main/res/layout/item_private_message_received.xml create mode 100644 app/src/main/res/layout/item_private_message_sent.xml diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewPrivateMessagesActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewPrivateMessagesActivity.java index 1a98eec0..c5a78e1a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewPrivateMessagesActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewPrivateMessagesActivity.java @@ -13,10 +13,11 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.AutoTransition; +import androidx.transition.TransitionManager; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.r0adkll.slidr.Slidr; import javax.inject.Inject; @@ -27,6 +28,7 @@ import butterknife.ButterKnife; import ml.docilealligator.infinityforreddit.ActivityToolbarInterface; import ml.docilealligator.infinityforreddit.Adapter.PrivateMessagesDetailRecyclerViewAdapter; import ml.docilealligator.infinityforreddit.AsyncTask.GetCurrentAccountAsyncTask; +import ml.docilealligator.infinityforreddit.AsyncTask.LoadUserDataAsyncTask; import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.Message; @@ -40,6 +42,8 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit public static final String EXTRA_PRIVATE_MESSAGE = "EPM"; private static final String NULL_ACCESS_TOKEN_STATE = "NATS"; private static final String ACCESS_TOKEN_STATE = "ATS"; + private static final String ACCOUNT_NAME_STATE = "ANS"; + private static final String USER_AVATAR_STATE = "UAS"; @BindView(R.id.coordinator_layout_view_private_messages_activity) CoordinatorLayout mCoordinatorLayout; @BindView(R.id.collapsing_toolbar_layout_view_private_messages_activity) @@ -50,8 +54,6 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit Toolbar mToolbar; @BindView(R.id.recycler_view_view_private_messages) RecyclerView mRecyclerView; - @BindView(R.id.fab_view_private_messages_activity) - FloatingActionButton mFab; @Inject @Named("oauth") Retrofit mOauthRetrofit; @@ -67,6 +69,8 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit private Message privateMessage; private boolean mNullAccessToken = false; private String mAccessToken; + private String mAccountName; + private String mUserAvatar; @Override protected void onCreate(Bundle savedInstanceState) { @@ -98,9 +102,6 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit int navBarHeight = getNavBarHeight(); if (navBarHeight > 0) { mRecyclerView.setPadding(0, 0, 0, navBarHeight); - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams(); - params.bottomMargin = navBarHeight; - mFab.setLayoutParams(params); } } } @@ -109,7 +110,7 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit privateMessage = intent.getParcelableExtra(EXTRA_PRIVATE_MESSAGE); if (privateMessage != null) { - mToolbar.setTitle(privateMessage.getTitle()); + mToolbar.setTitle(privateMessage.getSubject()); } setSupportActionBar(mToolbar); setToolbarGoToTop(mToolbar); @@ -117,6 +118,8 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit if (savedInstanceState != null) { mNullAccessToken = savedInstanceState.getBoolean(NULL_ACCESS_TOKEN_STATE); mAccessToken = savedInstanceState.getString(ACCESS_TOKEN_STATE); + mAccountName = savedInstanceState.getString(ACCOUNT_NAME_STATE); + mUserAvatar = savedInstanceState.getString(USER_AVATAR_STATE); if (!mNullAccessToken && mAccessToken == null) { getCurrentAccountAndBindView(); @@ -134,20 +137,34 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit mNullAccessToken = true; } else { mAccessToken = account.getAccessToken(); + mAccountName = account.getUsername(); } bindView(); }).execute(); } private void bindView() { - mAdapter = new PrivateMessagesDetailRecyclerViewAdapter(this, privateMessage, mCustomThemeWrapper); + mAdapter = new PrivateMessagesDetailRecyclerViewAdapter(this, privateMessage, mAccountName, mCustomThemeWrapper); mLinearLayoutManager = new LinearLayoutManager(this); - mLinearLayoutManager.setReverseLayout(true); - mLinearLayoutManager.setStackFromEnd(true); mRecyclerView.setLayoutManager(mLinearLayoutManager); mRecyclerView.setAdapter(mAdapter); } + public void fetchUserAvatar(String username, ProvideUserAvatarCallback provideUserAvatarCallback) { + if (mUserAvatar == null) { + new LoadUserDataAsyncTask(mRedditDataRoomDatabase.userDao(), username, mOauthRetrofit, iconImageUrl -> { + mUserAvatar = iconImageUrl; + provideUserAvatarCallback.fetchAvatarSuccess(iconImageUrl); + }).execute(); + } else { + provideUserAvatarCallback.fetchAvatarSuccess(mUserAvatar); + } + } + + public void delayTransition() { + TransitionManager.beginDelayedTransition(mRecyclerView, new AutoTransition()); + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -158,6 +175,15 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit 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); + outState.putString(ACCOUNT_NAME_STATE, mAccountName); + outState.putString(USER_AVATAR_STATE, mUserAvatar); + } + @Override protected SharedPreferences getDefaultSharedPreferences() { return mSharedPreferences; @@ -172,7 +198,6 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit protected void applyCustomTheme() { mCoordinatorLayout.setBackgroundColor(mCustomThemeWrapper.getBackgroundColor()); applyAppBarLayoutAndToolbarTheme(mAppBarLayout, mToolbar); - applyFABTheme(mFab); } @Override @@ -181,4 +206,8 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit mLinearLayoutManager.scrollToPositionWithOffset(0, 0); } } + + public interface ProvideUserAvatarCallback { + void fetchAvatarSuccess(String userAvatarUrl); + } } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PrivateMessagesDetailRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PrivateMessagesDetailRecyclerViewAdapter.java index 821d0e08..270d89ac 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PrivateMessagesDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Adapter/PrivateMessagesDetailRecyclerViewAdapter.java @@ -1,18 +1,24 @@ package ml.docilealligator.infinityforreddit.Adapter; -import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.net.Uri; import android.text.style.SuperscriptSpan; import android.text.util.Linkify; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.RequestOptions; + import butterknife.BindView; import butterknife.ButterKnife; import io.noties.markwon.AbstractMarkwonPlugin; @@ -21,38 +27,46 @@ import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.simple.ext.SimpleExtPlugin; +import jp.wasabeef.glide.transformations.RoundedCornersTransformation; import ml.docilealligator.infinityforreddit.Activity.LinkResolverActivity; +import ml.docilealligator.infinityforreddit.Activity.ViewPrivateMessagesActivity; import ml.docilealligator.infinityforreddit.Activity.ViewUserDetailActivity; import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.Message; import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.Utils.Utils; public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter { + private static final int VIEW_TYPE_MESSAGE_SENT = 0; + private static final int VIEW_TYPE_MESSAGE_RECEIVED = 1; private Message mMessage; - private Context mContext; + private ViewPrivateMessagesActivity mViewPrivateMessagesActivity; + private RequestManager mGlide; + private String mAccountName; private Markwon mMarkwon; private int mMessageBackgroundColor; - private int mUsernameColor; - private int mPrimaryTextColor; private int mSecondaryTextColor; private int mUnreadMessageBackgroundColor; - public PrivateMessagesDetailRecyclerViewAdapter(Context context, Message message, CustomThemeWrapper customThemeWrapper) { + public PrivateMessagesDetailRecyclerViewAdapter(ViewPrivateMessagesActivity viewPrivateMessagesActivity, Message message, String accountName, + CustomThemeWrapper customThemeWrapper) { mMessage = message; - mContext = context; - mMarkwon = Markwon.builder(mContext) + mViewPrivateMessagesActivity = viewPrivateMessagesActivity; + mGlide = Glide.with(viewPrivateMessagesActivity); + mAccountName = accountName; + mMarkwon = Markwon.builder(viewPrivateMessagesActivity) .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.linkResolver((view, link) -> { - Intent intent = new Intent(mContext, LinkResolverActivity.class); + Intent intent = new Intent(viewPrivateMessagesActivity, LinkResolverActivity.class); Uri uri = Uri.parse(link); if (uri.getScheme() == null && uri.getHost() == null) { intent.setData(LinkResolverActivity.getRedditUriByPath(link)); } else { intent.setData(uri); } - mContext.startActivity(intent); + viewPrivateMessagesActivity.startActivity(intent); }); } }) @@ -66,46 +80,91 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt ) .build(); mMessageBackgroundColor = customThemeWrapper.getCardViewBackgroundColor(); - mUsernameColor = customThemeWrapper.getUsername(); - mPrimaryTextColor = customThemeWrapper.getPrimaryTextColor(); mSecondaryTextColor = customThemeWrapper.getSecondaryTextColor(); mUnreadMessageBackgroundColor = customThemeWrapper.getUnreadMessageBackgroundColor(); } + @Override + public int getItemViewType(int position) { + if (position == 0) { + return mMessage.getAuthor().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED; + } else { + return mMessage.getReplies().get(position - 1).getAuthor().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED; + } + } + @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new DataViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_message, parent, false)); + if (viewType == VIEW_TYPE_MESSAGE_SENT) { + return new SentMessageViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_private_message_sent, parent, false)); + } else { + return new ReceivedMessageViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_private_message_received, parent, false)); + } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (holder instanceof DataViewHolder) { - Message message; - if (holder.getAdapterPosition() == 0) { - message = mMessage; - } else { - message = mMessage.getReplies().get(holder.getAdapterPosition() - 1); - } - - if (message != null) { + Message message; + if (holder.getAdapterPosition() == 0) { + message = mMessage; + } else { + message = mMessage.getReplies().get(holder.getAdapterPosition() - 1); + } + if (message != null) { + if (holder instanceof MessageViewHolder) { if (message.isNew()) { - ((DataViewHolder) holder).itemView.setBackgroundColor( + ((MessageViewHolder) holder).itemView.setBackgroundColor( mUnreadMessageBackgroundColor); } + mMarkwon.setMarkdown(((MessageViewHolder) holder).messageTextView, message.getBody()); - ((DataViewHolder) holder).authorTextView.setText(message.getAuthor()); - String subject = message.getSubject().substring(0, 1).toUpperCase() + message.getSubject().substring(1); - ((DataViewHolder) holder).subjectTextView.setText(subject); - mMarkwon.setMarkdown(((DataViewHolder) holder).contentCustomMarkwonView, message.getBody()); + ((MessageViewHolder) holder).messageTextView.setOnClickListener(view -> ((MessageViewHolder) holder).itemView.performClick()); - ((DataViewHolder) holder).authorTextView.setOnClickListener(view -> { - Intent intent = new Intent(mContext, ViewUserDetailActivity.class); - intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getAuthor()); - mContext.startActivity(intent); + ((MessageViewHolder) holder).messageTextView.setOnClickListener(view -> { + Log.i("asfasdf", "asdf " + ((MessageViewHolder) holder).timeTextView.getHeight()); + if (((MessageViewHolder) holder).timeTextView.getVisibility() != View.VISIBLE) { + ((MessageViewHolder) holder).timeTextView.setVisibility(View.VISIBLE); + mViewPrivateMessagesActivity.delayTransition(); + } else { + ((MessageViewHolder) holder).timeTextView.setVisibility(View.GONE); + mViewPrivateMessagesActivity.delayTransition(); + } + /*if (((MessageViewHolder) holder).timeTextView.getHeight() == 0) { + ((MessageViewHolder) holder).timeTextView.getLayoutParams().height = ConstraintLayout.LayoutParams.WRAP_CONTENT; + mViewPrivateMessagesActivity.delayTransition(); + } else { + mViewPrivateMessagesActivity.delayTransition(); + ((MessageViewHolder) holder).timeTextView.getLayoutParams().height = 0; + }*/ }); - ((DataViewHolder) holder).contentCustomMarkwonView.setOnClickListener(view -> ((DataViewHolder) holder).itemView.performClick()); + ((MessageViewHolder) holder).timeTextView.setText(Utils.getElapsedTime(mViewPrivateMessagesActivity, message.getTimeUTC())); + } + + if (holder instanceof SentMessageViewHolder) { + ((SentMessageViewHolder) holder).messageTextView.setBackgroundResource(R.drawable.private_message_ballon_sent); + } else if (holder instanceof ReceivedMessageViewHolder) { + mViewPrivateMessagesActivity.fetchUserAvatar(message.getAuthor(), userAvatarUrl -> { + if (userAvatarUrl == null || userAvatarUrl.equals("")) { + mGlide.load(R.drawable.subreddit_default_icon) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .into(((ReceivedMessageViewHolder) holder).userAvatarImageView); + } else { + mGlide.load(userAvatarUrl) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .error(mGlide.load(R.drawable.subreddit_default_icon) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))) + .into(((ReceivedMessageViewHolder) holder).userAvatarImageView); + } + }); + + ((ReceivedMessageViewHolder) holder).userAvatarImageView.setOnClickListener(view -> { + Intent intent = new Intent(mViewPrivateMessagesActivity, ViewUserDetailActivity.class); + intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getAuthor()); + mViewPrivateMessagesActivity.startActivity(intent); + }); + ((ReceivedMessageViewHolder) holder).messageTextView.setBackgroundResource(R.drawable.private_message_ballon_received); } } } @@ -138,27 +197,62 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt notifyItemInserted(currentSize); } - class DataViewHolder extends RecyclerView.ViewHolder { - @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) - TextView contentCustomMarkwonView; + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + super.onViewRecycled(holder); + if (holder instanceof MessageViewHolder) { + ((MessageViewHolder) holder).messageTextView.setBackground(null); + //((MessageViewHolder) holder).timeTextView.getLayoutParams().height = 0; + ((MessageViewHolder) holder).timeTextView.setVisibility(View.GONE); + } + if (holder instanceof ReceivedMessageViewHolder) { + mGlide.clear(((ReceivedMessageViewHolder) holder).userAvatarImageView); + } + } - DataViewHolder(View itemView) { + class MessageViewHolder extends RecyclerView.ViewHolder { + TextView messageTextView; + TextView timeTextView; + + public MessageViewHolder(@NonNull View itemView) { super(itemView); - ButterKnife.bind(this, itemView); + } - titleTextView.setVisibility(View.GONE); + void setBaseView(TextView messageTextView, TextView timeTextView) { + this.messageTextView = messageTextView; + this.timeTextView = timeTextView; itemView.setBackgroundColor(mMessageBackgroundColor); - authorTextView.setTextColor(mUsernameColor); - subjectTextView.setTextColor(mPrimaryTextColor); - titleTextView.setTextColor(mPrimaryTextColor); - contentCustomMarkwonView.setTextColor(mSecondaryTextColor); + messageTextView.setTextColor(Color.WHITE); + timeTextView.setTextColor(mSecondaryTextColor); + } + } + + class SentMessageViewHolder extends MessageViewHolder { + @BindView(R.id.message_text_view_item_private_message_sent) + TextView messageTextView; + @BindView(R.id.time_text_view_item_private_message_sent) + TextView timeTextView; + + SentMessageViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + setBaseView(messageTextView, timeTextView); + } + } + + class ReceivedMessageViewHolder extends MessageViewHolder { + @BindView(R.id.avatar_image_view_item_private_message_received) + ImageView userAvatarImageView; + @BindView(R.id.message_text_view_item_private_message_received) + TextView messageTextView; + @BindView(R.id.time_text_view_item_private_message_received) + TextView timeTextView; + + ReceivedMessageViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + setBaseView(messageTextView, timeTextView); } } } diff --git a/app/src/main/res/drawable/private_message_ballon_received.xml b/app/src/main/res/drawable/private_message_ballon_received.xml new file mode 100644 index 00000000..522d5cf0 --- /dev/null +++ b/app/src/main/res/drawable/private_message_ballon_received.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/private_message_ballon_sent.xml b/app/src/main/res/drawable/private_message_ballon_sent.xml new file mode 100644 index 00000000..e813c552 --- /dev/null +++ b/app/src/main/res/drawable/private_message_ballon_sent.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_view_private_messages.xml b/app/src/main/res/layout/activity_view_private_messages.xml index 67c665a9..3819bc4d 100644 --- a/app/src/main/res/layout/activity_view_private_messages.xml +++ b/app/src/main/res/layout/activity_view_private_messages.xml @@ -40,12 +40,4 @@ android:clipToPadding="false" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_private_message_received.xml b/app/src/main/res/layout/item_private_message_received.xml new file mode 100644 index 00000000..f3ba1c88 --- /dev/null +++ b/app/src/main/res/layout/item_private_message_received.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_private_message_sent.xml b/app/src/main/res/layout/item_private_message_sent.xml new file mode 100644 index 00000000..62951ba4 --- /dev/null +++ b/app/src/main/res/layout/item_private_message_sent.xml @@ -0,0 +1,43 @@ + + + + + + + + \ No newline at end of file