mirror of
				https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy.git
				synced 2025-10-31 17:08:11 +01:00 
			
		
		
		
	Rebase to master
This commit is contained in:
		| @@ -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 { | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -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 | ||||
|     ); | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|         }*/ | ||||
|     } | ||||
| } | ||||
| @@ -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"); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -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") | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -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" | ||||
|                     ) | ||||
|                 ) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user