Rebase to master

This commit is contained in:
Balazs Toldi 2023-08-15 15:33:05 +02:00
parent 81e2fdcf91
commit 92fcb811c3
No known key found for this signature in database
GPG Key ID: 6C7D440036F99D58
13 changed files with 1028 additions and 7 deletions

View File

@ -77,6 +77,7 @@ import eu.toldi.infinityforlemmy.fragments.InboxFragment;
import eu.toldi.infinityforlemmy.fragments.MorePostsInfoFragment;
import eu.toldi.infinityforlemmy.fragments.MultiRedditListingFragment;
import eu.toldi.infinityforlemmy.fragments.PostFragment;
import eu.toldi.infinityforlemmy.fragments.PrivateMessageFragment;
import eu.toldi.infinityforlemmy.fragments.SidebarFragment;
import eu.toldi.infinityforlemmy.fragments.SubredditListingFragment;
import eu.toldi.infinityforlemmy.fragments.SubscribedSubredditsListingFragment;
@ -319,6 +320,7 @@ public interface AppComponent {
void inject(CommentMoreBottomSheetFragment commentMoreBottomSheetFragment);
void inject(PrivateMessageFragment privateMessageFragment);
@Component.Factory
interface Factory {

View File

@ -10,6 +10,7 @@ import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import eu.toldi.infinityforlemmy.apis.StreamableAPI;
import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.comment.LemmyCommentAPI;
import eu.toldi.infinityforlemmy.post.LemmyPostAPI;
import eu.toldi.infinityforlemmy.utils.APIUtils;
@ -226,4 +227,10 @@ abstract class NetworkModule {
static LemmyCommentAPI provideCommentAPI(@Named("no_oauth") RetrofitHolder retrofitHolder) {
return new LemmyCommentAPI(retrofitHolder);
}
@Provides
@Singleton
static LemmyPrivateMessageAPI provideLemmyPrivateMessageAPI(@Named("base") RetrofitHolder retrofit) {
return new LemmyPrivateMessageAPI(retrofit);
}
}

View File

@ -55,6 +55,7 @@ import eu.toldi.infinityforlemmy.events.PassPrivateMessageEvent;
import eu.toldi.infinityforlemmy.events.PassPrivateMessageIndexEvent;
import eu.toldi.infinityforlemmy.events.SwitchAccountEvent;
import eu.toldi.infinityforlemmy.fragments.InboxFragment;
import eu.toldi.infinityforlemmy.fragments.PrivateMessageFragment;
import eu.toldi.infinityforlemmy.message.CommentInteraction;
import eu.toldi.infinityforlemmy.message.FetchMessage;
import eu.toldi.infinityforlemmy.message.ReadMessage;
@ -441,7 +442,7 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
@NonNull
@Override
public Fragment createFragment(int position) {
InboxFragment fragment = new InboxFragment();
Fragment fragment = new InboxFragment();
Bundle bundle = new Bundle();
bundle.putString(InboxFragment.EXTRA_ACCESS_TOKEN, mAccessToken);
switch (position) {
@ -450,11 +451,9 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
break;
case 1:
bundle.putString(InboxFragment.EXTRA_MESSAGE_WHERE, FetchMessage.WHERE_MENTIONS);
fragment.setArguments(bundle);
break;
case 2:
bundle.putString(InboxFragment.EXTRA_MESSAGE_WHERE, FetchMessage.WHERE_MESSAGES);
fragment.setArguments(bundle);
fragment = new PrivateMessageFragment();
break;
}
fragment.setArguments(bundle);

View File

@ -0,0 +1,344 @@
package eu.toldi.infinityforlemmy.adapters;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import eu.toldi.infinityforlemmy.NetworkState;
import eu.toldi.infinityforlemmy.R;
import eu.toldi.infinityforlemmy.activities.BaseActivity;
import eu.toldi.infinityforlemmy.activities.LinkResolverActivity;
import eu.toldi.infinityforlemmy.activities.ViewPrivateMessagesActivity;
import eu.toldi.infinityforlemmy.activities.ViewUserDetailActivity;
import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper;
import eu.toldi.infinityforlemmy.markdown.RedditHeadingPlugin;
import eu.toldi.infinityforlemmy.markdown.SpoilerAwareMovementMethod;
import eu.toldi.infinityforlemmy.markdown.SpoilerParserPlugin;
import eu.toldi.infinityforlemmy.markdown.SuperscriptPlugin;
import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.image.glide.GlideImagesPlugin;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.linkify.LinkifyPlugin;
import io.noties.markwon.movement.MovementMethodPlugin;
import retrofit2.Retrofit;
public class PrivateMessageRecycleViewAdapter extends PagedListAdapter<PrivateMessage, 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 static final DiffUtil.ItemCallback<PrivateMessage> DIFF_CALLBACK = new DiffUtil.ItemCallback<>() {
@Override
public boolean areItemsTheSame(@NonNull PrivateMessage message, @NonNull PrivateMessage t1) {
return message.getId() == t1.getId();
}
@Override
public boolean areContentsTheSame(@NonNull PrivateMessage message, @NonNull PrivateMessage t1) {
return message.getContent().equals(t1.getContent());
}
};
private BaseActivity mActivity;
private Retrofit retrofit;
private Markwon mMarkwon;
private String mAccessToken;
private final LemmyPrivateMessageAPI lemmyPrivateMessageAPI;
private int mMessageType;
private NetworkState networkState;
private RetryLoadingMoreCallback mRetryLoadingMoreCallback;
private int mColorAccent;
private int mMessageBackgroundColor;
private int mUsernameColor;
private int mPrimaryTextColor;
private int mSecondaryTextColor;
private int mUnreadMessageBackgroundColor;
private int mColorPrimaryLightTheme;
private int mButtonTextColor;
private boolean markAllMessagesAsRead = false;
public PrivateMessageRecycleViewAdapter(BaseActivity activity, Retrofit oauthRetrofit,
CustomThemeWrapper customThemeWrapper,
String accessToken,
LemmyPrivateMessageAPI lemmyPrivateMessageAPI, RetryLoadingMoreCallback retryLoadingMoreCallback) {
super(DIFF_CALLBACK);
mActivity = activity;
retrofit = oauthRetrofit;
this.lemmyPrivateMessageAPI = lemmyPrivateMessageAPI;
mRetryLoadingMoreCallback = retryLoadingMoreCallback;
mColorAccent = customThemeWrapper.getColorAccent();
mMessageBackgroundColor = customThemeWrapper.getCardViewBackgroundColor();
mUsernameColor = customThemeWrapper.getUsername();
mPrimaryTextColor = customThemeWrapper.getPrimaryTextColor();
mSecondaryTextColor = customThemeWrapper.getSecondaryTextColor();
int spoilerBackgroundColor = mSecondaryTextColor | 0xFF000000;
mUnreadMessageBackgroundColor = customThemeWrapper.getUnreadMessageBackgroundColor();
mColorPrimaryLightTheme = customThemeWrapper.getColorPrimaryLightTheme();
mButtonTextColor = customThemeWrapper.getButtonTextColor();
// todo:https://github.com/Docile-Alligator/Infinity-For-Reddit/issues/1027
// add tables support and replace with MarkdownUtils#commonPostMarkwonBuilder
mMarkwon = Markwon.builder(mActivity)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
}))
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> {
Intent intent = new Intent(mActivity, LinkResolverActivity.class);
Uri uri = Uri.parse(link);
intent.setData(uri);
mActivity.startActivity(intent);
});
}
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder.linkColor(customThemeWrapper.getLinkColor());
}
})
.usePlugin(SuperscriptPlugin.create())
.usePlugin(SpoilerParserPlugin.create(mSecondaryTextColor, spoilerBackgroundColor))
.usePlugin(RedditHeadingPlugin.create())
.usePlugin(StrikethroughPlugin.create())
.usePlugin(MovementMethodPlugin.create(new SpoilerAwareMovementMethod()))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.usePlugin(GlideImagesPlugin.create(mActivity))
.build();
mAccessToken = accessToken;
}
@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) {
PrivateMessage message = getItem(holder.getBindingAdapterPosition());
if (message != null) {
if (message.getRead()) {
if (markAllMessagesAsRead) {
message.setRead(true);
} else {
holder.itemView.setBackgroundColor(
mUnreadMessageBackgroundColor);
}
}
((DataViewHolder) holder).titleTextView.setVisibility(View.GONE);
((DataViewHolder) holder).authorTextView.setText(message.getCreatorQualifiedName());
String subject = message.getRecipientQualifiedName();
((DataViewHolder) holder).subjectTextView.setText(subject);
mMarkwon.setMarkdown(((DataViewHolder) holder).contentCustomMarkwonView, message.getContent());
holder.itemView.setOnClickListener(view -> {
Intent intent = new Intent(mActivity, ViewPrivateMessagesActivity.class);
intent.putExtra(ViewPrivateMessagesActivity.EXTRA_PRIVATE_MESSAGE_INDEX, holder.getBindingAdapterPosition());
intent.putExtra(ViewPrivateMessagesActivity.EXTRA_MESSAGE_POSITION, holder.getBindingAdapterPosition());
mActivity.startActivity(intent);
if (message.getRead()) {
holder.itemView.setBackgroundColor(mMessageBackgroundColor);
lemmyPrivateMessageAPI.markPrivateMessageAsRead(mAccessToken, message.getId(), new LemmyPrivateMessageAPI.PrivateMessageMarkedAsReadListener() {
@Override
public void onPrivateMessageMarkedAsReadError() {
message.setRead(false);
}
@Override
public void onPrivateMessageMarkedAsReadSuccess() {
message.setRead(true);
}
});
}
});
((DataViewHolder) holder).authorTextView.setOnClickListener(view -> {
Intent intent = new Intent(mActivity, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getCreatorName());
intent.putExtra(ViewUserDetailActivity.EXTRA_QUALIFIED_USER_NAME_KEY, message.getCreatorQualifiedName());
mActivity.startActivity(intent);
});
}
}
}
@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(mMessageBackgroundColor);
((DataViewHolder) holder).titleTextView.setVisibility(View.VISIBLE);
}
}
private boolean hasExtraRow() {
return networkState != null && networkState.getStatus() != NetworkState.Status.SUCCESS;
}
public 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);
}
}
public void updateMessageReply(PrivateMessage newReply, int position) {
if (position >= 0 && position < super.getItemCount()) {
PrivateMessage message = getItem(position);
if (message != null) {
notifyItemChanged(position);
}
}
}
public void setMarkAllMessagesAsRead(boolean markAllMessagesAsRead) {
this.markAllMessagesAsRead = markAllMessagesAsRead;
}
public interface RetryLoadingMoreCallback {
void retryLoadingMore();
}
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;
DataViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
if (mActivity.typeface != null) {
authorTextView.setTypeface(mActivity.typeface);
subjectTextView.setTypeface(mActivity.typeface);
titleTextView.setTypeface(mActivity.titleTypeface);
contentCustomMarkwonView.setTypeface(mActivity.contentTypeface);
}
itemView.setBackgroundColor(mMessageBackgroundColor);
authorTextView.setTextColor(mUsernameColor);
subjectTextView.setTextColor(mPrimaryTextColor);
titleTextView.setTextColor(mPrimaryTextColor);
contentCustomMarkwonView.setTextColor(mSecondaryTextColor);
contentCustomMarkwonView.setMovementMethod(LinkMovementMethod.getInstance());
contentCustomMarkwonView.setOnClickListener(view -> {
if (contentCustomMarkwonView.getSelectionStart() == -1 && contentCustomMarkwonView.getSelectionEnd() == -1) {
itemView.performClick();
}
});
}
}
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);
if (mActivity.typeface != null) {
errorTextView.setTypeface(mActivity.typeface);
retryButton.setTypeface(mActivity.typeface);
}
errorTextView.setText(R.string.load_comments_failed);
errorTextView.setTextColor(mSecondaryTextColor);
retryButton.setOnClickListener(view -> mRetryLoadingMoreCallback.retryLoadingMore());
retryButton.setBackgroundTintList(ColorStateList.valueOf(mColorPrimaryLightTheme));
retryButton.setTextColor(mButtonTextColor);
}
}
class LoadingViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.progress_bar_item_footer_loading)
ProgressBar progressBar;
LoadingViewHolder(@NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
progressBar.setIndeterminateTintList(ColorStateList.valueOf(mColorAccent));
}
}
}

View File

@ -15,6 +15,10 @@ import eu.toldi.infinityforlemmy.dto.EditCommentDTO;
import eu.toldi.infinityforlemmy.dto.EditPostDTO;
import eu.toldi.infinityforlemmy.dto.FollowCommunityDTO;
import eu.toldi.infinityforlemmy.dto.PostVoteDTO;
import eu.toldi.infinityforlemmy.dto.PrivateMessageDTO;
import eu.toldi.infinityforlemmy.dto.PrivateMessageReadDTO;
import eu.toldi.infinityforlemmy.dto.PrivateMessageReportDTO;
import eu.toldi.infinityforlemmy.dto.PrivateMessageUpdateDTO;
import eu.toldi.infinityforlemmy.dto.ReadCommentDTO;
import eu.toldi.infinityforlemmy.dto.ReadMessageDTO;
import eu.toldi.infinityforlemmy.dto.ReadPostDTO;
@ -246,4 +250,37 @@ public interface LemmyAPI {
Call<String> getSiteInfo(
@Query("auth") String auth
);
@GET("api/v3/private_message/list")
Call<String> privateMessagesList(
@Query("page") Integer page,
@Query("limit") Integer limit,
@Query("unread_only") Boolean unread_only,
@NonNull @Query("auth") String auth
);
@POST("api/v3/private_message")
Call<String> privateMessageSend(
@Body PrivateMessageDTO params
);
@PUT("api/v3/private_message")
Call<String> privateMessageEdit(
@Body PrivateMessageUpdateDTO params
);
@POST("api/v3/private_message/delete")
Call<String> privateMessageDelete(
@Body PrivateMessageUpdateDTO params
);
@POST("api/v3/private_message/mark_as_read")
Call<String> privateMessageMarkAsRead(
@Body PrivateMessageReadDTO params
);
@POST("api/v3/private_message/report")
Call<String> privateMessageReport(
@Body PrivateMessageReportDTO params
);
}

View File

@ -0,0 +1,255 @@
package eu.toldi.infinityforlemmy.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.PagedList;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import eu.toldi.infinityforlemmy.FragmentCommunicator;
import eu.toldi.infinityforlemmy.Infinity;
import eu.toldi.infinityforlemmy.NetworkState;
import eu.toldi.infinityforlemmy.R;
import eu.toldi.infinityforlemmy.RecyclerViewContentScrollingInterface;
import eu.toldi.infinityforlemmy.RedditDataRoomDatabase;
import eu.toldi.infinityforlemmy.RetrofitHolder;
import eu.toldi.infinityforlemmy.activities.BaseActivity;
import eu.toldi.infinityforlemmy.adapters.PrivateMessageRecycleViewAdapter;
import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper;
import eu.toldi.infinityforlemmy.customviews.LinearLayoutManagerBugFixed;
import eu.toldi.infinityforlemmy.events.RepliedToPrivateMessageEvent;
import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
import eu.toldi.infinityforlemmy.privatemessage.PrivateMessageViewModel;
public class PrivateMessageFragment extends Fragment implements FragmentCommunicator {
public static final String EXTRA_ACCESS_TOKEN = "EAT";
public static final String EXTRA_MESSAGE_WHERE = "EMT";
@BindView(R.id.swipe_refresh_layout_inbox_fragment)
SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recycler_view_inbox_fragment)
RecyclerView mRecyclerView;
@BindView(R.id.fetch_messages_info_linear_layout_inbox_fragment)
LinearLayout mFetchMessageInfoLinearLayout;
@BindView(R.id.fetch_messages_info_image_view_inbox_fragment)
ImageView mFetchMessageInfoImageView;
@BindView(R.id.fetch_messages_info_text_view_inbox_fragment)
TextView mFetchMessageInfoTextView;
PrivateMessageViewModel mMessageViewModel;
@Inject
@Named("no_oauth")
RetrofitHolder mRetrofit;
@Inject
RedditDataRoomDatabase mRedditDataRoomDatabase;
@Inject
@Named("default")
SharedPreferences mSharedPreferences;
@Inject
CustomThemeWrapper mCustomThemeWrapper;
@Inject
LemmyPrivateMessageAPI mLemmyPrivateMessageAPI;
private String mAccessToken;
private PrivateMessageRecycleViewAdapter mAdapter;
private RequestManager mGlide;
private LinearLayoutManagerBugFixed mLinearLayoutManager;
private BaseActivity mActivity;
public PrivateMessageFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.i("PrivateMessageFragment", "onCreateView");
View rootView = inflater.inflate(R.layout.fragment_inbox, container, false);
((Infinity) mActivity.getApplication()).getAppComponent().inject(this);
ButterKnife.bind(this, rootView);
EventBus.getDefault().register(this);
applyTheme();
Bundle arguments = getArguments();
if (arguments == null) {
return rootView;
}
mAccessToken = getArguments().getString(EXTRA_ACCESS_TOKEN);
mGlide = Glide.with(this);
if (mActivity.isImmersiveInterface()) {
mRecyclerView.setPadding(0, 0, 0, mActivity.getNavBarHeight());
}
mAdapter = new PrivateMessageRecycleViewAdapter(mActivity, mRetrofit.getRetrofit(), mCustomThemeWrapper, mAccessToken, mLemmyPrivateMessageAPI, () -> mMessageViewModel.retryLoadingMore());
mLinearLayoutManager = new LinearLayoutManagerBugFixed(mActivity);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setAdapter(mAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mActivity, mLinearLayoutManager.getOrientation());
mRecyclerView.addItemDecoration(dividerItemDecoration);
if (mActivity instanceof RecyclerViewContentScrollingInterface) {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) {
((RecyclerViewContentScrollingInterface) mActivity).contentScrollDown();
} else if (dy < 0) {
((RecyclerViewContentScrollingInterface) mActivity).contentScrollUp();
}
}
});
}
PrivateMessageViewModel.Factory factory = new PrivateMessageViewModel.Factory(mRetrofit.getRetrofit(),
getResources().getConfiguration().locale, mAccessToken, mLemmyPrivateMessageAPI);
mMessageViewModel = new ViewModelProvider(this, factory).get(PrivateMessageViewModel.class);
mMessageViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> mAdapter.submitList(messages));
mMessageViewModel.hasMessage().observe(getViewLifecycleOwner(), hasMessage -> {
mSwipeRefreshLayout.setRefreshing(false);
if (hasMessage) {
mFetchMessageInfoLinearLayout.setVisibility(View.GONE);
} else {
mFetchMessageInfoLinearLayout.setOnClickListener(null);
showErrorView(R.string.no_messages);
}
});
mMessageViewModel.getInitialLoadingState().observe(getViewLifecycleOwner(), networkState -> {
if (networkState.getStatus().equals(NetworkState.Status.SUCCESS)) {
mSwipeRefreshLayout.setRefreshing(false);
} else if (networkState.getStatus().equals(NetworkState.Status.FAILED)) {
mSwipeRefreshLayout.setRefreshing(false);
mFetchMessageInfoLinearLayout.setOnClickListener(view -> {
mFetchMessageInfoLinearLayout.setVisibility(View.GONE);
mMessageViewModel.refresh();
mAdapter.setNetworkState(null);
});
showErrorView(R.string.load_messages_failed);
} else {
mSwipeRefreshLayout.setRefreshing(true);
}
});
mMessageViewModel.getPaginationNetworkState().observe(getViewLifecycleOwner(), networkState -> {
mAdapter.setNetworkState(networkState);
});
mSwipeRefreshLayout.setOnRefreshListener(this::onRefresh);
return rootView;
}
private void showErrorView(int stringResId) {
mSwipeRefreshLayout.setRefreshing(false);
mFetchMessageInfoLinearLayout.setVisibility(View.VISIBLE);
mFetchMessageInfoTextView.setText(stringResId);
mGlide.load(R.drawable.error_image).into(mFetchMessageInfoImageView);
}
@Override
public void applyTheme() {
mSwipeRefreshLayout.setProgressBackgroundColorSchemeColor(mCustomThemeWrapper.getCircularProgressBarBackground());
mSwipeRefreshLayout.setColorSchemeColors(mCustomThemeWrapper.getColorAccent());
mFetchMessageInfoTextView.setTextColor(mCustomThemeWrapper.getSecondaryTextColor());
if (mActivity.typeface != null) {
mFetchMessageInfoTextView.setTypeface(mActivity.typeface);
}
}
public void goBackToTop() {
if (mLinearLayoutManager != null) {
mLinearLayoutManager.scrollToPositionWithOffset(0, 0);
}
}
public void markAllMessagesRead() {
if (mAdapter != null) {
mAdapter.setMarkAllMessagesAsRead(true);
int previousPosition = -1;
if (mLinearLayoutManager != null) {
previousPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
}
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
mRecyclerView.setAdapter(null);
mRecyclerView.setLayoutManager(null);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(layoutManager);
if (previousPosition > 0) {
mRecyclerView.scrollToPosition(previousPosition);
}
}
}
private void onRefresh() {
mMessageViewModel.refresh();
mAdapter.setNetworkState(null);
}
public PrivateMessage getMessageByIndex(int index) {
if (mMessageViewModel == null || index < 0) {
return null;
}
PagedList<PrivateMessage> messages = mMessageViewModel.getMessages().getValue();
if (messages == null) {
return null;
}
if (index >= messages.size()) {
return null;
}
return messages.get(index);
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
mActivity = (BaseActivity) context;
}
@Subscribe
public void onRepliedToPrivateMessageEvent(RepliedToPrivateMessageEvent repliedToPrivateMessageEvent) {
/* if (mAdapter != null && mWhere.equals(FetchMessage.WHERE_MESSAGES)) {
mAdapter.updateMessageReply(repliedToPrivateMessageEvent.newReply, repliedToPrivateMessageEvent.messagePosition);
}*/
}
}

View File

@ -220,9 +220,6 @@ public class ParsePost {
String authorAvatar = (!data.getJSONObject("creator").isNull("avatar")) ? data.getJSONObject("creator").getString("avatar") : null;
Uri uri = Uri.parse(url);
if (uri.getAuthority() == null) {
Log.e("ParsePost", "parseData:" + uri.toString());
}
String path = uri.getPath();
boolean isVideo = path.endsWith(".mp4") || path.endsWith(".webm") || path.endsWith(".gifv");

View File

@ -0,0 +1,23 @@
package eu.toldi.infinityforlemmy.dto
data class PrivateMessageDTO(val recipient_id: Int, val content: String, val auth: String)
data class PrivateMessageUpdateDTO(
val private_message_id: Int,
val auth: String,
val content: String
)
data class PrivateMessageDeleteDTO(
val private_message_id: Int,
val auth: String,
val deleted: Boolean
)
data class PrivateMessageReadDTO(val private_message_id: Int, val auth: String, val read: Boolean)
data class PrivateMessageReportDTO(
val private_message_id: Int,
val auth: String,
val reason: String
)

View File

@ -0,0 +1,107 @@
package eu.toldi.infinityforlemmy.privatemessage
import eu.toldi.infinityforlemmy.RetrofitHolder
import eu.toldi.infinityforlemmy.apis.LemmyAPI
import eu.toldi.infinityforlemmy.dto.PrivateMessageReadDTO
import org.json.JSONObject
class LemmyPrivateMessageAPI(val retrofitHolder: RetrofitHolder) {
fun fetchPrivateMessages(
auth: String,
page: Int,
listener: PrivateMessageFetchedListener,
limit: Int = 25,
unreadOnly: Boolean = false
) {
val api = retrofitHolder.retrofit.create(LemmyAPI::class.java)
api.privateMessagesList(page, limit, unreadOnly, auth).enqueue(
object : retrofit2.Callback<String> {
override fun onResponse(
call: retrofit2.Call<String>,
response: retrofit2.Response<String>
) {
if (response.isSuccessful) {
val jresponse = JSONObject(response.body()!!);
val privateMessages = jresponse.getJSONArray("private_messages")
val privateMessageList = mutableListOf<PrivateMessage>()
for (i in 0 until privateMessages.length()) {
val privateMessage =
parsePrivateMessage(privateMessages.getJSONObject(i))
privateMessageList.add(privateMessage)
}
listener.onPrivateMessageFetchedSuccess(privateMessageList)
} else {
listener.onPrivateMessageFetchedError()
}
}
override fun onFailure(call: retrofit2.Call<String>, t: Throwable) {
listener.onPrivateMessageFetchedError()
}
}
)
}
fun markPrivateMessageAsRead(
auth: String,
privateMessageId: Int,
listener: PrivateMessageMarkedAsReadListener
) {
val api = retrofitHolder.retrofit.create(LemmyAPI::class.java)
api.privateMessageMarkAsRead(PrivateMessageReadDTO(privateMessageId, auth, true)).enqueue(
object : retrofit2.Callback<String> {
override fun onResponse(
call: retrofit2.Call<String>,
response: retrofit2.Response<String>
) {
if (response.isSuccessful) {
listener.onPrivateMessageMarkedAsReadSuccess()
} else {
listener.onPrivateMessageMarkedAsReadError()
}
}
override fun onFailure(call: retrofit2.Call<String>, t: Throwable) {
listener.onPrivateMessageMarkedAsReadError()
}
}
)
}
interface PrivateMessageMarkedAsReadListener {
fun onPrivateMessageMarkedAsReadSuccess()
fun onPrivateMessageMarkedAsReadError()
}
interface PrivateMessageFetchedListener {
fun onPrivateMessageFetchedSuccess(privateMessage: List<PrivateMessage>)
fun onPrivateMessageFetchedError()
}
private fun parsePrivateMessage(jsonObject: JSONObject): PrivateMessage {
val privateMessage = jsonObject.getJSONObject("private_message")
val creator = jsonObject.getJSONObject("creator")
val recipient = jsonObject.getJSONObject("recipient")
return PrivateMessage(
id = privateMessage.getInt("id"),
creatorId = privateMessage.getInt("creator_id"),
recipientId = privateMessage.getInt("recipient_id"),
content = privateMessage.getString("content"),
deleted = privateMessage.getBoolean("deleted"),
read = privateMessage.getBoolean("read"),
published = privateMessage.getString("published"),
updated = privateMessage.optString("updated", ""),
creatorName = creator.getString("name"),
creatorAvatar = creator.optString("avatar", ""),
creatorQualifiedName = creator.getString("actor_id"),
recipientName = recipient.getString("name"),
recipientAvatar = recipient.optString("avatar", ""),
recipientQualifiedName = recipient.getString("actor_id")
)
}
}

View File

@ -0,0 +1,18 @@
package eu.toldi.infinityforlemmy.privatemessage
data class PrivateMessage(
val id: Int,
val creatorId: Int,
val recipientId: Int,
val content: String,
val deleted: Boolean,
var read: Boolean,
val published: String,
val updated: String,
val creatorName: String,
val creatorAvatar: String,
val creatorQualifiedName: String,
val recipientName: String,
val recipientAvatar: String,
val recipientQualifiedName: String
)

View File

@ -0,0 +1,103 @@
package eu.toldi.infinityforlemmy.privatemessage
import androidx.lifecycle.MutableLiveData
import androidx.paging.PageKeyedDataSource
import eu.toldi.infinityforlemmy.NetworkState
import retrofit2.Retrofit
import java.util.Locale
class PrivateMessageDataSource(
private val retrofit: Retrofit,
private val locale: Locale,
private val accessToken: String,
private val mLemmyPrivateMessageAPI: LemmyPrivateMessageAPI
) :
PageKeyedDataSource<Int, PrivateMessage>() {
val paginationNetworkStateLiveData: MutableLiveData<NetworkState>
val initialLoadStateLiveData: MutableLiveData<NetworkState>
private val hasPostLiveData: MutableLiveData<Boolean>
private var params: LoadParams<Int>? = null
private var callback: LoadCallback<Int, PrivateMessage>? = null
private val page = 1
init {
paginationNetworkStateLiveData = MutableLiveData()
initialLoadStateLiveData = MutableLiveData()
hasPostLiveData = MutableLiveData()
}
fun hasPostLiveData(): MutableLiveData<Boolean> {
return hasPostLiveData
}
fun retryLoadingMore() {
loadAfter(params!!, callback!!)
}
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, PrivateMessage>
) {
initialLoadStateLiveData.postValue(NetworkState.LOADING)
mLemmyPrivateMessageAPI.fetchPrivateMessages(accessToken, page, object :
LemmyPrivateMessageAPI.PrivateMessageFetchedListener {
override fun onPrivateMessageFetchedSuccess(privateMessages: List<PrivateMessage>) {
hasPostLiveData.postValue(true)
if (privateMessages.isEmpty()) {
callback.onResult(ArrayList(), null, null)
} else {
callback.onResult(privateMessages, null, page + 1)
}
initialLoadStateLiveData.postValue(NetworkState.LOADED)
}
override fun onPrivateMessageFetchedError() {
initialLoadStateLiveData.postValue(
NetworkState(
NetworkState.Status.FAILED,
"Error fetch messages"
)
)
}
})
}
override fun loadBefore(
params: LoadParams<Int>,
callback: LoadCallback<Int, PrivateMessage>
) {
}
override fun loadAfter(
params: LoadParams<Int>,
callback: LoadCallback<Int, PrivateMessage>
) {
this.params = params
this.callback = callback
paginationNetworkStateLiveData.postValue(NetworkState.LOADING)
mLemmyPrivateMessageAPI.fetchPrivateMessages(accessToken, params.key, object :
LemmyPrivateMessageAPI.PrivateMessageFetchedListener {
override fun onPrivateMessageFetchedSuccess(privateMessages: List<PrivateMessage>) {
hasPostLiveData.postValue(true)
if (privateMessages.isEmpty()) {
callback.onResult(ArrayList(), null)
} else {
callback.onResult(privateMessages, params.key + 1)
}
paginationNetworkStateLiveData.postValue(NetworkState.LOADED)
}
override fun onPrivateMessageFetchedError() {
paginationNetworkStateLiveData.postValue(
NetworkState(
NetworkState.Status.FAILED,
"Error fetch messages"
)
)
}
})
}
}

View File

@ -0,0 +1,37 @@
package eu.toldi.infinityforlemmy.privatemessage
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import retrofit2.Retrofit
import java.util.Locale
internal class PrivateMessageDataSourceFactory(
retrofit: Retrofit,
locale: Locale,
accessToken: String,
private var mLemmyPrivateMessageAPI: LemmyPrivateMessageAPI
) : DataSource.Factory<Int, PrivateMessage>() {
private val mPrivateMessageDataSource: PrivateMessageDataSource =
PrivateMessageDataSource(retrofit, locale, accessToken, mLemmyPrivateMessageAPI)
private val messageDataSourceLiveData: MutableLiveData<PrivateMessageDataSource> =
MutableLiveData<PrivateMessageDataSource>()
override fun create(): DataSource<Int, PrivateMessage> {
messageDataSourceLiveData.postValue(mPrivateMessageDataSource)
return mPrivateMessageDataSource
}
fun getMessageDataSourceLiveData(): MutableLiveData<PrivateMessageDataSource> {
return messageDataSourceLiveData
}
fun getMessageDataSource(): PrivateMessageDataSource {
return mPrivateMessageDataSource
}
fun changeLemmPrivateMessageAPI(lemmyPrivateMessageAPI: LemmyPrivateMessageAPI) {
this.mLemmyPrivateMessageAPI = lemmyPrivateMessageAPI
}
}

View File

@ -0,0 +1,92 @@
package eu.toldi.infinityforlemmy.privatemessage
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 eu.toldi.infinityforlemmy.NetworkState
import retrofit2.Retrofit
import java.util.Locale
class PrivateMessageViewModel(
retrofit: Retrofit,
locale: Locale,
accessToken: String,
lemmyPrivateMessageAPI: LemmyPrivateMessageAPI
) :
ViewModel() {
private val messageDataSourceFactory: PrivateMessageDataSourceFactory
val paginationNetworkState: LiveData<NetworkState>
val initialLoadingState: LiveData<NetworkState>
private val hasMessageLiveData: LiveData<Boolean>
val messages: LiveData<PagedList<PrivateMessage>>
private val whereLiveData: MutableLiveData<LemmyPrivateMessageAPI> = MutableLiveData()
init {
messageDataSourceFactory =
PrivateMessageDataSourceFactory(retrofit, locale, accessToken, lemmyPrivateMessageAPI)
initialLoadingState = Transformations.switchMap(
messageDataSourceFactory.getMessageDataSourceLiveData(),
PrivateMessageDataSource::initialLoadStateLiveData
)
paginationNetworkState = Transformations.switchMap(
messageDataSourceFactory.getMessageDataSourceLiveData(),
PrivateMessageDataSource::paginationNetworkStateLiveData
)
hasMessageLiveData = Transformations.switchMap(
messageDataSourceFactory.getMessageDataSourceLiveData(),
PrivateMessageDataSource::hasPostLiveData
)
whereLiveData.postValue(lemmyPrivateMessageAPI)
val pagedListConfig: PagedList.Config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(25)
.build()
messages = Transformations.switchMap<LemmyPrivateMessageAPI, PagedList<PrivateMessage>>(
whereLiveData
) { newWhere: LemmyPrivateMessageAPI? ->
messageDataSourceFactory.changeLemmPrivateMessageAPI(whereLiveData.value!!)
LivePagedListBuilder(
messageDataSourceFactory,
pagedListConfig
).build()
}
}
fun hasMessage(): LiveData<Boolean> {
return hasMessageLiveData
}
fun refresh() {
messageDataSourceFactory.getMessageDataSource().invalidate()
}
fun retryLoadingMore() {
messageDataSourceFactory.getMessageDataSource().retryLoadingMore()
}
class Factory(
private val retrofit: Retrofit,
private val locale: Locale,
private val accessToken: String,
private val lemmyPrivateMessageAPI: LemmyPrivateMessageAPI
) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PrivateMessageViewModel(
retrofit,
locale,
accessToken,
lemmyPrivateMessageAPI
) as T
}
}
}