Merge remote-tracking branch 'codeberg/master'

This commit is contained in:
Balazs Toldi 2023-08-18 21:51:38 +02:00
commit 2392d9fa29
No known key found for this signature in database
GPG Key ID: 6C7D440036F99D58
22 changed files with 1401 additions and 210 deletions

View File

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

View File

@ -10,6 +10,7 @@ import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import eu.toldi.infinityforlemmy.apis.StreamableAPI; import eu.toldi.infinityforlemmy.apis.StreamableAPI;
import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.comment.LemmyCommentAPI; import eu.toldi.infinityforlemmy.comment.LemmyCommentAPI;
import eu.toldi.infinityforlemmy.post.LemmyPostAPI; import eu.toldi.infinityforlemmy.post.LemmyPostAPI;
import eu.toldi.infinityforlemmy.utils.APIUtils; import eu.toldi.infinityforlemmy.utils.APIUtils;
@ -226,4 +227,10 @@ abstract class NetworkModule {
static LemmyCommentAPI provideCommentAPI(@Named("no_oauth") RetrofitHolder retrofitHolder) { static LemmyCommentAPI provideCommentAPI(@Named("no_oauth") RetrofitHolder retrofitHolder) {
return new LemmyCommentAPI(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.PassPrivateMessageIndexEvent;
import eu.toldi.infinityforlemmy.events.SwitchAccountEvent; import eu.toldi.infinityforlemmy.events.SwitchAccountEvent;
import eu.toldi.infinityforlemmy.fragments.InboxFragment; import eu.toldi.infinityforlemmy.fragments.InboxFragment;
import eu.toldi.infinityforlemmy.fragments.PrivateMessageFragment;
import eu.toldi.infinityforlemmy.message.CommentInteraction; import eu.toldi.infinityforlemmy.message.CommentInteraction;
import eu.toldi.infinityforlemmy.message.FetchMessage; import eu.toldi.infinityforlemmy.message.FetchMessage;
import eu.toldi.infinityforlemmy.message.ReadMessage; import eu.toldi.infinityforlemmy.message.ReadMessage;
@ -180,7 +181,7 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
if (i == EditorInfo.IME_ACTION_DONE) { if (i == EditorInfo.IME_ACTION_DONE) {
Utils.hideKeyboard(this); Utils.hideKeyboard(this);
Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class); Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class);
pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, thingEditText.getText().toString()); //pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, thingEditText.getText().toString());
startActivity(pmIntent); startActivity(pmIntent);
return true; return true;
} }
@ -193,7 +194,7 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
-> { -> {
Utils.hideKeyboard(this); Utils.hideKeyboard(this);
Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class); Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class);
pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, thingEditText.getText().toString()); //pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, thingEditText.getText().toString());
startActivity(pmIntent); startActivity(pmIntent);
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -330,7 +331,7 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
if (resultCode == RESULT_OK && requestCode == SEARCH_USER_REQUEST_CODE && data != null) { if (resultCode == RESULT_OK && requestCode == SEARCH_USER_REQUEST_CODE && data != null) {
String username = data.getStringExtra(SearchActivity.EXTRA_RETURN_USER_NAME); String username = data.getStringExtra(SearchActivity.EXTRA_RETURN_USER_NAME);
Intent intent = new Intent(this, SendPrivateMessageActivity.class); Intent intent = new Intent(this, SendPrivateMessageActivity.class);
intent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, username); //intent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, username);
startActivity(intent); startActivity(intent);
} }
} }
@ -441,7 +442,7 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
@NonNull @NonNull
@Override @Override
public Fragment createFragment(int position) { public Fragment createFragment(int position) {
InboxFragment fragment = new InboxFragment(); Fragment fragment = new InboxFragment();
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(InboxFragment.EXTRA_ACCESS_TOKEN, mAccessToken); bundle.putString(InboxFragment.EXTRA_ACCESS_TOKEN, mAccessToken);
switch (position) { switch (position) {
@ -450,11 +451,9 @@ public class InboxActivity extends BaseActivity implements ActivityToolbarInterf
break; break;
case 1: case 1:
bundle.putString(InboxFragment.EXTRA_MESSAGE_WHERE, FetchMessage.WHERE_MENTIONS); bundle.putString(InboxFragment.EXTRA_MESSAGE_WHERE, FetchMessage.WHERE_MENTIONS);
fragment.setArguments(bundle);
break; break;
case 2: case 2:
bundle.putString(InboxFragment.EXTRA_MESSAGE_WHERE, FetchMessage.WHERE_MESSAGES); fragment = new PrivateMessageFragment();
fragment.setArguments(bundle);
break; break;
} }
fragment.setArguments(bundle); fragment.setArguments(bundle);

View File

@ -1,8 +1,13 @@
package eu.toldi.infinityforlemmy.activities; package eu.toldi.infinityforlemmy.activities;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -10,12 +15,20 @@ import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -23,13 +36,24 @@ import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import eu.toldi.infinityforlemmy.Infinity; import eu.toldi.infinityforlemmy.Infinity;
import eu.toldi.infinityforlemmy.R; import eu.toldi.infinityforlemmy.R;
import eu.toldi.infinityforlemmy.RetrofitHolder;
import eu.toldi.infinityforlemmy.UploadImageEnabledActivity;
import eu.toldi.infinityforlemmy.UploadedImage;
import eu.toldi.infinityforlemmy.adapters.MarkdownBottomBarRecyclerViewAdapter;
import eu.toldi.infinityforlemmy.bottomsheetfragments.UploadedImagesBottomSheetFragment;
import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper; import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper;
import eu.toldi.infinityforlemmy.message.ComposeMessage; import eu.toldi.infinityforlemmy.customviews.LinearLayoutManagerBugFixed;
import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
import eu.toldi.infinityforlemmy.user.BasicUserInfo;
import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils; import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils;
import retrofit2.Retrofit; import eu.toldi.infinityforlemmy.utils.Utils;
public class SendPrivateMessageActivity extends BaseActivity { public class SendPrivateMessageActivity extends BaseActivity implements UploadImageEnabledActivity {
public static final String EXTRA_RECIPIENT_USERNAME = "ERU"; public static final String EXTRA_RECIPIENT_USER_INFO = "ERUI";
private static final int PICK_IMAGE_REQUEST_CODE = 100;
private static final int CAPTURE_IMAGE_REQUEST_CODE = 200;
@BindView(R.id.coordinator_layout_send_private_message_activity) @BindView(R.id.coordinator_layout_send_private_message_activity)
CoordinatorLayout coordinatorLayout; CoordinatorLayout coordinatorLayout;
@BindView(R.id.appbar_layout_send_private_message_activity) @BindView(R.id.appbar_layout_send_private_message_activity)
@ -40,15 +64,20 @@ public class SendPrivateMessageActivity extends BaseActivity {
EditText usernameEditText; EditText usernameEditText;
@BindView(R.id.divider_1_send_private_message_activity) @BindView(R.id.divider_1_send_private_message_activity)
View divider1; View divider1;
@BindView(R.id.subjet_edit_text_send_private_message_activity)
EditText subjectEditText;
@BindView(R.id.divider_2_send_private_message_activity) @BindView(R.id.divider_2_send_private_message_activity)
View divider2; View divider2;
@BindView(R.id.content_edit_text_send_private_message_activity) @BindView(R.id.content_edit_text_send_private_message_activity)
EditText messageEditText; EditText messageEditText;
@BindView(R.id.markdown_bottom_bar_recycler_view_send_private_message_activity)
RecyclerView markdownBottomBarRecyclerView;
@Inject @Inject
@Named("oauth") @Named("no_oauth")
Retrofit mOauthRetrofit; RetrofitHolder mRetrofit;
@Inject
Executor mExecutor;
@Inject @Inject
@Named("default") @Named("default")
SharedPreferences mSharedPreferences; SharedPreferences mSharedPreferences;
@ -57,9 +86,18 @@ public class SendPrivateMessageActivity extends BaseActivity {
SharedPreferences mCurrentAccountSharedPreferences; SharedPreferences mCurrentAccountSharedPreferences;
@Inject @Inject
CustomThemeWrapper mCustomThemeWrapper; CustomThemeWrapper mCustomThemeWrapper;
@Inject
LemmyPrivateMessageAPI mLemmyPrivateMessageAPI;
private String mAccessToken; private String mAccessToken;
private BasicUserInfo mRecipientBasicUserInfo;
private boolean isSubmitting = false; private boolean isSubmitting = false;
private ArrayList<UploadedImage> uploadedImages = new ArrayList<>();
private Uri capturedImageUri;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
((Infinity) getApplication()).getAppComponent().inject(this); ((Infinity) getApplication()).getAppComponent().inject(this);
@ -77,14 +115,66 @@ public class SendPrivateMessageActivity extends BaseActivity {
addOnOffsetChangedListener(appBarLayout); addOnOffsetChangedListener(appBarLayout);
} }
MarkdownBottomBarRecyclerViewAdapter adapter = new MarkdownBottomBarRecyclerViewAdapter(
mCustomThemeWrapper, new MarkdownBottomBarRecyclerViewAdapter.ItemClickListener() {
@Override
public void onClick(int item) {
MarkdownBottomBarRecyclerViewAdapter.bindEditTextWithItemClickListener(
SendPrivateMessageActivity.this, messageEditText, item);
}
@Override
public void onUploadImage() {
Utils.hideKeyboard(SendPrivateMessageActivity.this);
UploadedImagesBottomSheetFragment fragment = new UploadedImagesBottomSheetFragment();
Bundle arguments = new Bundle();
arguments.putParcelableArrayList(UploadedImagesBottomSheetFragment.EXTRA_UPLOADED_IMAGES,
uploadedImages);
fragment.setArguments(arguments);
fragment.show(getSupportFragmentManager(), fragment.getTag());
}
});
markdownBottomBarRecyclerView.setLayoutManager(new LinearLayoutManagerBugFixed(this,
LinearLayoutManagerBugFixed.HORIZONTAL, false));
markdownBottomBarRecyclerView.setAdapter(adapter);
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
if (savedInstanceState != null) {
String username = getIntent().getStringExtra(EXTRA_RECIPIENT_USERNAME); mRecipientBasicUserInfo = savedInstanceState.getParcelable(EXTRA_RECIPIENT_USER_INFO);
if (username != null) { } else {
usernameEditText.setText(username); mRecipientBasicUserInfo = getIntent().getParcelableExtra(EXTRA_RECIPIENT_USER_INFO);
} }
if (mRecipientBasicUserInfo != null) {
usernameEditText.setText(mRecipientBasicUserInfo.getQualifiedName());
usernameEditText.setEnabled(false);
} else {
finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == PICK_IMAGE_REQUEST_CODE) {
if (data == null) {
Toast.makeText(SendPrivateMessageActivity.this, R.string.error_getting_image, Toast.LENGTH_LONG).show();
return;
}
Utils.uploadImageToReddit(this, mExecutor, mRetrofit,
mAccessToken, messageEditText, coordinatorLayout, data.getData(), uploadedImages);
} else if (requestCode == CAPTURE_IMAGE_REQUEST_CODE) {
Utils.uploadImageToReddit(this, mExecutor, mRetrofit,
mAccessToken, messageEditText, coordinatorLayout, capturedImageUri, uploadedImages);
}
}
} }
@Override @Override
@ -108,12 +198,6 @@ public class SendPrivateMessageActivity extends BaseActivity {
return true; return true;
} }
if (subjectEditText.getText() == null || subjectEditText.getText().toString().equals("")) {
isSubmitting = false;
Snackbar.make(coordinatorLayout, R.string.message_subject_required, Snackbar.LENGTH_LONG).show();
return true;
}
if (messageEditText.getText() == null || messageEditText.getText().toString().equals("")) { if (messageEditText.getText() == null || messageEditText.getText().toString().equals("")) {
isSubmitting = false; isSubmitting = false;
Snackbar.make(coordinatorLayout, R.string.message_content_required, Snackbar.LENGTH_LONG).show(); Snackbar.make(coordinatorLayout, R.string.message_content_required, Snackbar.LENGTH_LONG).show();
@ -125,32 +209,26 @@ public class SendPrivateMessageActivity extends BaseActivity {
Snackbar sendingSnackbar = Snackbar.make(coordinatorLayout, R.string.sending_message, Snackbar.LENGTH_INDEFINITE); Snackbar sendingSnackbar = Snackbar.make(coordinatorLayout, R.string.sending_message, Snackbar.LENGTH_INDEFINITE);
sendingSnackbar.show(); sendingSnackbar.show();
ComposeMessage.composeMessage(mOauthRetrofit, mAccessToken, getResources().getConfiguration().locale, mLemmyPrivateMessageAPI.sendPrivateMessage(mAccessToken, mRecipientBasicUserInfo.getId(), messageEditText.getText().toString(), new LemmyPrivateMessageAPI.PrivateMessageSentListener() {
usernameEditText.getText().toString(), subjectEditText.getText().toString(), @Override
messageEditText.getText().toString(), new ComposeMessage.ComposeMessageListener() { public void onPrivateMessageSentSuccess(@NonNull PrivateMessage privateMessage) {
@Override isSubmitting = false;
public void composeMessageSuccess() { item.setEnabled(true);
isSubmitting = false; item.getIcon().setAlpha(255);
item.setEnabled(true); Toast.makeText(SendPrivateMessageActivity.this, R.string.send_message_success, Toast.LENGTH_SHORT).show();
item.getIcon().setAlpha(255); finish();
Toast.makeText(SendPrivateMessageActivity.this, R.string.send_message_success, Toast.LENGTH_SHORT).show(); }
finish();
}
@Override @Override
public void composeMessageFailed(String errorMessage) { public void onPrivateMessageSentError() {
isSubmitting = false; isSubmitting = false;
sendingSnackbar.dismiss(); sendingSnackbar.dismiss();
item.setEnabled(true); item.setEnabled(true);
item.getIcon().setAlpha(255); item.getIcon().setAlpha(255);
if (errorMessage == null || errorMessage.equals("")) { Snackbar.make(coordinatorLayout, R.string.send_message_failed, Snackbar.LENGTH_LONG).show();
Snackbar.make(coordinatorLayout, R.string.send_message_failed, Snackbar.LENGTH_LONG).show(); }
} else { });
Snackbar.make(coordinatorLayout, errorMessage, Snackbar.LENGTH_LONG).show();
}
}
});
} }
} }
return false; return false;
@ -159,6 +237,7 @@ public class SendPrivateMessageActivity extends BaseActivity {
@Override @Override
protected void onSaveInstanceState(@NonNull Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_RECIPIENT_USER_INFO, mRecipientBasicUserInfo);
} }
@Override @Override
@ -177,19 +256,49 @@ public class SendPrivateMessageActivity extends BaseActivity {
applyAppBarLayoutAndCollapsingToolbarLayoutAndToolbarTheme(appBarLayout, null, toolbar); applyAppBarLayoutAndCollapsingToolbarLayoutAndToolbarTheme(appBarLayout, null, toolbar);
int primaryTextColor = mCustomThemeWrapper.getPrimaryTextColor(); int primaryTextColor = mCustomThemeWrapper.getPrimaryTextColor();
usernameEditText.setTextColor(primaryTextColor); usernameEditText.setTextColor(primaryTextColor);
subjectEditText.setTextColor(primaryTextColor);
messageEditText.setTextColor(primaryTextColor); messageEditText.setTextColor(primaryTextColor);
int secondaryTextColor = mCustomThemeWrapper.getSecondaryTextColor(); int secondaryTextColor = mCustomThemeWrapper.getSecondaryTextColor();
usernameEditText.setHintTextColor(secondaryTextColor); usernameEditText.setHintTextColor(secondaryTextColor);
subjectEditText.setHintTextColor(secondaryTextColor);
messageEditText.setHintTextColor(secondaryTextColor); messageEditText.setHintTextColor(secondaryTextColor);
int dividerColor = mCustomThemeWrapper.getDividerColor(); int dividerColor = mCustomThemeWrapper.getDividerColor();
divider1.setBackgroundColor(dividerColor); divider1.setBackgroundColor(dividerColor);
divider2.setBackgroundColor(dividerColor); divider2.setBackgroundColor(dividerColor);
if (typeface != null) { if (typeface != null) {
usernameEditText.setTypeface(typeface); usernameEditText.setTypeface(typeface);
subjectEditText.setTypeface(typeface);
messageEditText.setTypeface(typeface); messageEditText.setTypeface(typeface);
} }
} }
@Override
public void uploadImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,
getResources().getString(R.string.select_from_gallery)), PICK_IMAGE_REQUEST_CODE);
}
@Override
public void captureImage() {
Intent pictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try {
capturedImageUri = FileProvider.getUriForFile(this, "eu.toldi.infinityforlemmy.provider",
File.createTempFile("captured_image", ".jpg", getExternalFilesDir(Environment.DIRECTORY_PICTURES)));
pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
startActivityForResult(pictureIntent, CAPTURE_IMAGE_REQUEST_CODE);
} catch (IOException ex) {
Toast.makeText(this, R.string.error_creating_temp_file, Toast.LENGTH_SHORT).show();
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_camera_available, Toast.LENGTH_SHORT).show();
}
}
@Override
public void insertImageUrl(UploadedImage uploadedImage) {
int start = Math.max(messageEditText.getSelectionStart(), 0);
int end = Math.max(messageEditText.getSelectionEnd(), 0);
messageEditText.getText().replace(Math.min(start, end), Math.max(start, end),
"![" + uploadedImage.imageName + "](" + uploadedImage.imageUrl + ")",
0, "![]()".length() + uploadedImage.imageName.length() + uploadedImage.imageUrl.length());
}
} }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
@ -27,6 +28,7 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
@ -46,14 +48,15 @@ import eu.toldi.infinityforlemmy.customviews.LinearLayoutManagerBugFixed;
import eu.toldi.infinityforlemmy.events.PassPrivateMessageEvent; import eu.toldi.infinityforlemmy.events.PassPrivateMessageEvent;
import eu.toldi.infinityforlemmy.events.PassPrivateMessageIndexEvent; import eu.toldi.infinityforlemmy.events.PassPrivateMessageIndexEvent;
import eu.toldi.infinityforlemmy.events.RepliedToPrivateMessageEvent; import eu.toldi.infinityforlemmy.events.RepliedToPrivateMessageEvent;
import eu.toldi.infinityforlemmy.message.Message;
import eu.toldi.infinityforlemmy.message.ReadMessage; import eu.toldi.infinityforlemmy.message.ReadMessage;
import eu.toldi.infinityforlemmy.message.ReplyMessage; import eu.toldi.infinityforlemmy.privatemessage.LemmyPrivateMessageAPI;
import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils; import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils;
import retrofit2.Retrofit; import retrofit2.Retrofit;
public class ViewPrivateMessagesActivity extends BaseActivity implements ActivityToolbarInterface { public class ViewPrivateMessagesActivity extends BaseActivity implements ActivityToolbarInterface {
public static final String EXTRA_PRIVATE_MESSAGE = "EPM";
public static final String EXTRA_PRIVATE_MESSAGE_INDEX = "EPM"; public static final String EXTRA_PRIVATE_MESSAGE_INDEX = "EPM";
public static final String EXTRA_MESSAGE_POSITION = "EMP"; public static final String EXTRA_MESSAGE_POSITION = "EMP";
private static final String USER_AVATAR_STATE = "UAS"; private static final String USER_AVATAR_STATE = "UAS";
@ -91,14 +94,19 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
CustomThemeWrapper mCustomThemeWrapper; CustomThemeWrapper mCustomThemeWrapper;
@Inject @Inject
Executor mExecutor; Executor mExecutor;
@Inject
LemmyPrivateMessageAPI mLemmyPrivateMessageAPI;
private LinearLayoutManagerBugFixed mLinearLayoutManager; private LinearLayoutManagerBugFixed mLinearLayoutManager;
private PrivateMessagesDetailRecyclerViewAdapter mAdapter; private PrivateMessagesDetailRecyclerViewAdapter mAdapter;
@State @State
Message privateMessage; PrivateMessage privateMessage;
@State @State
Message replyTo; PrivateMessage replyTo;
private String mAccessToken; private String mAccessToken;
private String mAccountName; private String mAccountName;
private String mAccountQualifiedName;
private String mUserAvatar; private String mUserAvatar;
private ArrayList<ProvideUserAvatarCallback> mProvideUserAvatarCallbacks; private ArrayList<ProvideUserAvatarCallback> mProvideUserAvatarCallbacks;
private boolean isLoadingUserAvatar = false; private boolean isLoadingUserAvatar = false;
@ -128,6 +136,11 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
addOnOffsetChangedListener(mAppBarLayout); addOnOffsetChangedListener(mAppBarLayout);
} }
Intent intent = getIntent();
privateMessage = intent.getParcelableExtra(EXTRA_PRIVATE_MESSAGE);
Log.i("ViewPrivate", "privateMessage: " + privateMessage);
setSupportActionBar(mToolbar); setSupportActionBar(mToolbar);
setToolbarGoToTop(mToolbar); setToolbarGoToTop(mToolbar);
@ -135,6 +148,7 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_NAME, null); mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_NAME, null);
mAccountQualifiedName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_QUALIFIED_NAME, null);
if (savedInstanceState != null) { if (savedInstanceState != null) {
mUserAvatar = savedInstanceState.getString(USER_AVATAR_STATE); mUserAvatar = savedInstanceState.getString(USER_AVATAR_STATE);
@ -144,36 +158,37 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
bindView(); bindView();
} }
} else { } else {
if (privateMessage != null) {
bindView();
}
EventBus.getDefault().post(new PassPrivateMessageIndexEvent(getIntent().getIntExtra(EXTRA_PRIVATE_MESSAGE_INDEX, -1))); EventBus.getDefault().post(new PassPrivateMessageIndexEvent(getIntent().getIntExtra(EXTRA_PRIVATE_MESSAGE_INDEX, -1)));
} }
} }
private void bindView() { private void bindView() {
if (privateMessage != null) { if (privateMessage != null) {
if (privateMessage.getAuthor().equals(mAccountName)) { if (privateMessage.getCreatorQualifiedName().equals(mAccountQualifiedName)) {
setTitle(privateMessage.getDestination()); setTitle(privateMessage.getRecipientName());
mToolbar.setOnClickListener(view -> { mToolbar.setOnClickListener(view -> {
if (privateMessage.isDestinationDeleted()) {
return;
}
Intent intent = new Intent(this, ViewUserDetailActivity.class); Intent intent = new Intent(this, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, privateMessage.getDestination()); intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, privateMessage.getRecipientName());
intent.putExtra(ViewUserDetailActivity.EXTRA_QUALIFIED_USER_NAME_KEY, privateMessage.getRecipientQualifiedName());
startActivity(intent); startActivity(intent);
}); });
} else { } else {
setTitle(privateMessage.getAuthor()); setTitle(privateMessage.getCreatorName());
mToolbar.setOnClickListener(view -> { mToolbar.setOnClickListener(view -> {
if (privateMessage.isAuthorDeleted()) {
return;
}
Intent intent = new Intent(this, ViewUserDetailActivity.class); Intent intent = new Intent(this, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, privateMessage.getAuthor()); intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, privateMessage.getCreatorName());
intent.putExtra(ViewUserDetailActivity.EXTRA_QUALIFIED_USER_NAME_KEY, privateMessage.getCreatorQualifiedName());
startActivity(intent); startActivity(intent);
}); });
} }
} }
mAdapter = new PrivateMessagesDetailRecyclerViewAdapter(this, mSharedPreferences, mAdapter = new PrivateMessagesDetailRecyclerViewAdapter(this, mSharedPreferences,
getResources().getConfiguration().locale, privateMessage, mAccountName, mCustomThemeWrapper); getResources().getConfiguration().locale, privateMessage, mAccountQualifiedName, mCustomThemeWrapper);
mLinearLayoutManager = new LinearLayoutManagerBugFixed(this); mLinearLayoutManager = new LinearLayoutManagerBugFixed(this);
mLinearLayoutManager.setStackFromEnd(true); mLinearLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLinearLayoutManager); mRecyclerView.setLayoutManager(mLinearLayoutManager);
@ -184,45 +199,40 @@ public class ViewPrivateMessagesActivity extends BaseActivity implements Activit
if (!mEditText.getText().toString().equals("")) { if (!mEditText.getText().toString().equals("")) {
//Send Message //Send Message
if (privateMessage != null) { if (privateMessage != null) {
ArrayList<Message> replies = privateMessage.getReplies(); List<PrivateMessage> replies = privateMessage.getReplies();
if (replyTo == null) { if (replyTo == null) {
replyTo = privateMessage; replyTo = privateMessage;
} }
isSendingMessage = true; isSendingMessage = true;
mSendImageView.setColorFilter(mSecondaryTextColor, android.graphics.PorterDuff.Mode.SRC_IN); mSendImageView.setColorFilter(mSecondaryTextColor, android.graphics.PorterDuff.Mode.SRC_IN);
ReplyMessage.replyMessage(mEditText.getText().toString(), replyTo.getFullname(),
getResources().getConfiguration().locale, mOauthRetrofit, mAccessToken,
new ReplyMessage.ReplyMessageListener() {
@Override
public void replyMessageSuccess(Message message) {
if (mAdapter != null) {
mAdapter.addReply(message);
}
goToBottom();
mEditText.setText("");
mSendImageView.setColorFilter(mSendMessageIconColor, android.graphics.PorterDuff.Mode.SRC_IN);
isSendingMessage = false;
EventBus.getDefault().post(new RepliedToPrivateMessageEvent(message, getIntent().getIntExtra(EXTRA_MESSAGE_POSITION, -1)));
}
@Override mLemmyPrivateMessageAPI.sendPrivateMessage(mAccessToken, replyTo.getCreatorId(), mEditText.getText().toString(), new LemmyPrivateMessageAPI.PrivateMessageSentListener() {
public void replyMessageFailed(String errorMessage) {
if (errorMessage != null && !errorMessage.equals("")) { @Override
Snackbar.make(mCoordinatorLayout, errorMessage, Snackbar.LENGTH_LONG).show(); public void onPrivateMessageSentSuccess(@NonNull PrivateMessage privateMessage) {
} else { if (mAdapter != null) {
Snackbar.make(mCoordinatorLayout, R.string.reply_message_failed, Snackbar.LENGTH_LONG).show(); mAdapter.addReply(privateMessage);
} }
mSendImageView.setColorFilter(mSendMessageIconColor, android.graphics.PorterDuff.Mode.SRC_IN); goToBottom();
isSendingMessage = false; mEditText.setText("");
} isSendingMessage = false;
}); EventBus.getDefault().post(new RepliedToPrivateMessageEvent(privateMessage, getIntent().getIntExtra(EXTRA_MESSAGE_POSITION, -1)));
}
@Override
public void onPrivateMessageSentError() {
Snackbar.make(mCoordinatorLayout, R.string.reply_message_failed, Snackbar.LENGTH_LONG).show();
mSendImageView.setColorFilter(mSendMessageIconColor, android.graphics.PorterDuff.Mode.SRC_IN);
isSendingMessage = false;
}
});
StringBuilder fullnames = new StringBuilder(); StringBuilder fullnames = new StringBuilder();
if (privateMessage.isNew()) {
fullnames.append(privateMessage.getFullname()).append(",");
}
if (replies != null && !replies.isEmpty()) { if (replies != null && !replies.isEmpty()) {
for (Message m : replies) { for (PrivateMessage m : replies) {
if (m.isNew()) { if (!m.getRead()) {
fullnames.append(m).append(","); fullnames.append(m).append(",");
} }
} }

View File

@ -1227,8 +1227,8 @@ public class ViewSubredditDetailActivity extends BaseActivity implements SortTyp
} }
return true; return true;
} else if (itemId == R.id.action_contact_mods_view_subreddit_detail_activity) { } else if (itemId == R.id.action_contact_mods_view_subreddit_detail_activity) {
Intent intent = new Intent(this, SendPrivateMessageActivity.class); /* Intent intent = new Intent(this, SendPrivateMessageActivity.class);
intent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, "r/" + communityName); intent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, "r/" + communityName);*/
//startActivity(intent); //startActivity(intent);
return true; return true;
} else if (itemId == R.id.block_community_view_subreddit_detail_activity) { } else if (itemId == R.id.block_community_view_subreddit_detail_activity) {

View File

@ -108,6 +108,7 @@ import eu.toldi.infinityforlemmy.post.PostPagingSource;
import eu.toldi.infinityforlemmy.readpost.InsertReadPost; import eu.toldi.infinityforlemmy.readpost.InsertReadPost;
import eu.toldi.infinityforlemmy.subreddit.ParseSubredditData; import eu.toldi.infinityforlemmy.subreddit.ParseSubredditData;
import eu.toldi.infinityforlemmy.subreddit.SubredditData; import eu.toldi.infinityforlemmy.subreddit.SubredditData;
import eu.toldi.infinityforlemmy.user.BasicUserInfo;
import eu.toldi.infinityforlemmy.user.BlockUser; import eu.toldi.infinityforlemmy.user.BlockUser;
import eu.toldi.infinityforlemmy.user.FetchUserData; import eu.toldi.infinityforlemmy.user.FetchUserData;
import eu.toldi.infinityforlemmy.user.UserDao; import eu.toldi.infinityforlemmy.user.UserDao;
@ -1268,7 +1269,7 @@ public class ViewUserDetailActivity extends BaseActivity implements SortTypeSele
} }
Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class); Intent pmIntent = new Intent(this, SendPrivateMessageActivity.class);
pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USERNAME, username); pmIntent.putExtra(SendPrivateMessageActivity.EXTRA_RECIPIENT_USER_INFO, new BasicUserInfo(mUserData.getId(), username, qualifiedName, mUserData.getAvatar(), mUserData.getDisplayName()));
startActivity(pmIntent); startActivity(pmIntent);
return true; return true;
} else if (itemId == R.id.action_add_to_post_filter_view_user_detail_activity) { } else if (itemId == R.id.action_add_to_post_filter_view_user_detail_activity) {

View File

@ -0,0 +1,343 @@
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, message);
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

@ -27,34 +27,35 @@ import java.util.Locale;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
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.inlineparser.BangInlineProcessor;
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 jp.wasabeef.glide.transformations.RoundedCornersTransformation;
import eu.toldi.infinityforlemmy.R; import eu.toldi.infinityforlemmy.R;
import eu.toldi.infinityforlemmy.activities.LinkResolverActivity; import eu.toldi.infinityforlemmy.activities.LinkResolverActivity;
import eu.toldi.infinityforlemmy.activities.ViewPrivateMessagesActivity; import eu.toldi.infinityforlemmy.activities.ViewPrivateMessagesActivity;
import eu.toldi.infinityforlemmy.activities.ViewUserDetailActivity; import eu.toldi.infinityforlemmy.activities.ViewUserDetailActivity;
import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper; import eu.toldi.infinityforlemmy.customtheme.CustomThemeWrapper;
import eu.toldi.infinityforlemmy.markdown.ClickableGlideImagesPlugin;
import eu.toldi.infinityforlemmy.markdown.RedditHeadingPlugin; import eu.toldi.infinityforlemmy.markdown.RedditHeadingPlugin;
import eu.toldi.infinityforlemmy.markdown.SpoilerAwareMovementMethod; import eu.toldi.infinityforlemmy.markdown.SpoilerAwareMovementMethod;
import eu.toldi.infinityforlemmy.markdown.SpoilerParserPlugin; import eu.toldi.infinityforlemmy.markdown.SpoilerParserPlugin;
import eu.toldi.infinityforlemmy.markdown.SuperscriptPlugin; import eu.toldi.infinityforlemmy.markdown.SuperscriptPlugin;
import eu.toldi.infinityforlemmy.message.Message; import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils; import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils;
import eu.toldi.infinityforlemmy.utils.Utils; import eu.toldi.infinityforlemmy.utils.Utils;
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 jp.wasabeef.glide.transformations.RoundedCornersTransformation;
public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int VIEW_TYPE_MESSAGE_SENT = 0; private static final int VIEW_TYPE_MESSAGE_SENT = 0;
private static final int VIEW_TYPE_MESSAGE_RECEIVED = 1; private static final int VIEW_TYPE_MESSAGE_RECEIVED = 1;
private Message mMessage; private PrivateMessage mMessage;
private ViewPrivateMessagesActivity mViewPrivateMessagesActivity; private ViewPrivateMessagesActivity mViewPrivateMessagesActivity;
private RequestManager mGlide; private RequestManager mGlide;
private Locale mLocale; private Locale mLocale;
@ -70,7 +71,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
public PrivateMessagesDetailRecyclerViewAdapter(ViewPrivateMessagesActivity viewPrivateMessagesActivity, public PrivateMessagesDetailRecyclerViewAdapter(ViewPrivateMessagesActivity viewPrivateMessagesActivity,
SharedPreferences sharedPreferences, Locale locale, SharedPreferences sharedPreferences, Locale locale,
Message message, String accountName, PrivateMessage message, String accountName,
CustomThemeWrapper customThemeWrapper) { CustomThemeWrapper customThemeWrapper) {
mMessage = message; mMessage = message;
mViewPrivateMessagesActivity = viewPrivateMessagesActivity; mViewPrivateMessagesActivity = viewPrivateMessagesActivity;
@ -83,7 +84,6 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
mMarkwon = Markwon.builder(viewPrivateMessagesActivity) mMarkwon = Markwon.builder(viewPrivateMessagesActivity)
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> { .usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
plugin.excludeInlineProcessor(HtmlInlineProcessor.class); plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
plugin.excludeInlineProcessor(BangInlineProcessor.class);
})) }))
.usePlugin(new AbstractMarkwonPlugin() { .usePlugin(new AbstractMarkwonPlugin() {
@Override @Override
@ -108,6 +108,8 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
builder.linkColor(customThemeWrapper.getLinkColor()); builder.linkColor(customThemeWrapper.getLinkColor());
} }
}) })
.usePlugin(GlideImagesPlugin.create(viewPrivateMessagesActivity))
.usePlugin(ClickableGlideImagesPlugin.create(viewPrivateMessagesActivity))
.usePlugin(SuperscriptPlugin.create()) .usePlugin(SuperscriptPlugin.create())
.usePlugin(StrikethroughPlugin.create()) .usePlugin(StrikethroughPlugin.create())
.usePlugin(SpoilerParserPlugin.create(commentColor, commentColor | 0xFF000000)) .usePlugin(SpoilerParserPlugin.create(commentColor, commentColor | 0xFF000000))
@ -127,9 +129,9 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (position == 0) { if (position == 0) {
return mMessage.getAuthor().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED; return mMessage.getCreatorQualifiedName().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED;
} else { } else {
return mMessage.getReplies().get(position - 1).getAuthor().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED; return mMessage.getReplies().get(position - 1).getCreatorQualifiedName().equals(mAccountName) ? VIEW_TYPE_MESSAGE_SENT : VIEW_TYPE_MESSAGE_RECEIVED;
} }
} }
@ -145,7 +147,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Message message; PrivateMessage message;
if (holder.getBindingAdapterPosition() == 0) { if (holder.getBindingAdapterPosition() == 0) {
message = mMessage; message = mMessage;
} else { } else {
@ -153,12 +155,12 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
} }
if (message != null) { if (message != null) {
if (holder instanceof MessageViewHolder) { if (holder instanceof MessageViewHolder) {
mMarkwon.setMarkdown(((MessageViewHolder) holder).messageTextView, message.getBody()); mMarkwon.setMarkdown(((MessageViewHolder) holder).messageTextView, message.getContent());
if (mShowElapsedTime) { if (mShowElapsedTime) {
((MessageViewHolder) holder).timeTextView.setText(Utils.getElapsedTime(mViewPrivateMessagesActivity, message.getTimeUTC())); ((MessageViewHolder) holder).timeTextView.setText(Utils.getElapsedTime(mViewPrivateMessagesActivity, message.getPublished()));
} else { } else {
((MessageViewHolder) holder).timeTextView.setText(Utils.getFormattedTime(mLocale, message.getTimeUTC(), mTimeFormatPattern)); ((MessageViewHolder) holder).timeTextView.setText(Utils.getFormattedTime(mLocale, message.getPublished(), mTimeFormatPattern));
} }
} }
@ -166,26 +168,33 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
((SentMessageViewHolder) holder).messageTextView.setBackground(Utils.getTintedDrawable(mViewPrivateMessagesActivity, ((SentMessageViewHolder) holder).messageTextView.setBackground(Utils.getTintedDrawable(mViewPrivateMessagesActivity,
R.drawable.private_message_ballon, mSentMessageBackgroundColor)); R.drawable.private_message_ballon, mSentMessageBackgroundColor));
} else if (holder instanceof ReceivedMessageViewHolder) { } else if (holder instanceof ReceivedMessageViewHolder) {
mViewPrivateMessagesActivity.fetchUserAvatar(message.getAuthor(), userAvatarUrl -> { if (!message.getCreatorAvatar().equals("")) {
if (userAvatarUrl == null || userAvatarUrl.equals("")) { mGlide.load(message.getCreatorAvatar())
mGlide.load(R.drawable.subreddit_default_icon) .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) .error(mGlide.load(R.drawable.subreddit_default_icon)
.into(((ReceivedMessageViewHolder) holder).userAvatarImageView); .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))))
} else { .into(((ReceivedMessageViewHolder) holder).userAvatarImageView);
mGlide.load(userAvatarUrl) } else {
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) mViewPrivateMessagesActivity.fetchUserAvatar(message.getCreatorQualifiedName(), userAvatarUrl -> {
.error(mGlide.load(R.drawable.subreddit_default_icon) if (userAvatarUrl == null || userAvatarUrl.equals("")) {
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))) mGlide.load(R.drawable.subreddit_default_icon)
.into(((ReceivedMessageViewHolder) holder).userAvatarImageView); .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 -> { ((ReceivedMessageViewHolder) holder).userAvatarImageView.setOnClickListener(view -> {
if (message.isAuthorDeleted()) {
return;
}
Intent intent = new Intent(mViewPrivateMessagesActivity, ViewUserDetailActivity.class); Intent intent = new Intent(mViewPrivateMessagesActivity, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getAuthor()); intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, message.getCreatorName());
intent.putExtra(ViewUserDetailActivity.EXTRA_QUALIFIED_USER_NAME_KEY, message.getCreatorQualifiedName());
mViewPrivateMessagesActivity.startActivity(intent); mViewPrivateMessagesActivity.startActivity(intent);
}); });
@ -207,12 +216,12 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
} }
} }
public void setMessage(Message message) { public void setMessage(PrivateMessage message) {
mMessage = message; mMessage = message;
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void addReply(Message reply) { public void addReply(PrivateMessage reply) {
int currentSize = getItemCount(); int currentSize = getItemCount();
if (mMessage != null) { if (mMessage != null) {
@ -273,7 +282,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
copyImageView.setColorFilter(mSecondaryTextColor, android.graphics.PorterDuff.Mode.SRC_IN); copyImageView.setColorFilter(mSecondaryTextColor, android.graphics.PorterDuff.Mode.SRC_IN);
copyImageView.setOnClickListener(view -> { copyImageView.setOnClickListener(view -> {
Message message; PrivateMessage message;
if (getBindingAdapterPosition() == 0) { if (getBindingAdapterPosition() == 0) {
message = mMessage; message = mMessage;
} else { } else {
@ -282,7 +291,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
if (message != null) { if (message != null) {
ClipboardManager clipboard = (ClipboardManager) mViewPrivateMessagesActivity.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) mViewPrivateMessagesActivity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) { if (clipboard != null) {
ClipData clip = ClipData.newPlainText("simple text", message.getBody()); ClipData clip = ClipData.newPlainText("simple text", message.getContent());
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
if (android.os.Build.VERSION.SDK_INT < 33) { if (android.os.Build.VERSION.SDK_INT < 33) {
Toast.makeText(mViewPrivateMessagesActivity, R.string.copy_success, Toast.LENGTH_SHORT).show(); Toast.makeText(mViewPrivateMessagesActivity, R.string.copy_success, Toast.LENGTH_SHORT).show();

View File

@ -15,6 +15,10 @@ import eu.toldi.infinityforlemmy.dto.EditCommentDTO;
import eu.toldi.infinityforlemmy.dto.EditPostDTO; import eu.toldi.infinityforlemmy.dto.EditPostDTO;
import eu.toldi.infinityforlemmy.dto.FollowCommunityDTO; import eu.toldi.infinityforlemmy.dto.FollowCommunityDTO;
import eu.toldi.infinityforlemmy.dto.PostVoteDTO; 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.ReadCommentDTO;
import eu.toldi.infinityforlemmy.dto.ReadMessageDTO; import eu.toldi.infinityforlemmy.dto.ReadMessageDTO;
import eu.toldi.infinityforlemmy.dto.ReadPostDTO; import eu.toldi.infinityforlemmy.dto.ReadPostDTO;
@ -246,4 +250,37 @@ public interface LemmyAPI {
Call<String> getSiteInfo( Call<String> getSiteInfo(
@Query("auth") String auth @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

@ -1,12 +1,12 @@
package eu.toldi.infinityforlemmy.events; package eu.toldi.infinityforlemmy.events;
import eu.toldi.infinityforlemmy.message.Message; import eu.toldi.infinityforlemmy.privatemessage.PrivateMessage;
public class RepliedToPrivateMessageEvent { public class RepliedToPrivateMessageEvent {
public Message newReply; public PrivateMessage newReply;
public int messagePosition; public int messagePosition;
public RepliedToPrivateMessageEvent(Message newReply, int messagePosition) { public RepliedToPrivateMessageEvent(PrivateMessage newReply, int messagePosition) {
this.newReply = newReply; this.newReply = newReply;
this.messagePosition = messagePosition; this.messagePosition = messagePosition;
} }

View File

@ -0,0 +1,243 @@
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.refresh());
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.getPrivateMessages().observe(getViewLifecycleOwner(), messages -> mAdapter.submitList(messages));
mMessageViewModel.getInitialLoadState().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);
}
});
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();
mSwipeRefreshLayout.setRefreshing(false);
}
public PrivateMessage getMessageByIndex(int index) {
if (mMessageViewModel == null || index < 0) {
return null;
}
PagedList<PrivateMessage> messages = mMessageViewModel.getPrivateMessages().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; String authorAvatar = (!data.getJSONObject("creator").isNull("avatar")) ? data.getJSONObject("creator").getString("avatar") : null;
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
if (uri.getAuthority() == null) {
Log.e("ParsePost", "parseData:" + uri.toString());
}
String path = uri.getPath(); String path = uri.getPath();
boolean isVideo = path.endsWith(".mp4") || path.endsWith(".webm") || path.endsWith(".gifv"); boolean isVideo = path.endsWith(".mp4") || path.endsWith(".webm") || path.endsWith(".gifv");

View File

@ -1,5 +1,14 @@
package eu.toldi.infinityforlemmy.utils; package eu.toldi.infinityforlemmy.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class LemmyUtils { public class LemmyUtils {
public static String actorID2FullName(String url) { public static String actorID2FullName(String url) {
String[] splitURL = url.split("/"); String[] splitURL = url.split("/");
@ -21,4 +30,25 @@ public class LemmyUtils {
String domain = splitQualifiedName[1]; String domain = splitQualifiedName[1];
return "https://" + domain + "/u/" + userName; return "https://" + domain + "/u/" + userName;
} }
public static Long dateStringToMills(String dateStr) {
long postTimeMillis = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
postTimeMillis = ZonedDateTime.parse(dateStr,
DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of("Z"))).toInstant().toEpochMilli();
} else {
dateStr = dateStr.substring(0, dateStr.lastIndexOf(".") + 4) + 'Z';
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
Date date = sdf.parse(dateStr);
if (date != null) {
postTimeMillis = date.getTime();
}
} catch (ParseException e) {
e.printStackTrace();
}
}
return postTimeMillis;
}
} }

View File

@ -49,7 +49,11 @@ public class UploadImageUtils {
if (uploadMediaResponse.isSuccessful()) { if (uploadMediaResponse.isSuccessful()) {
JSONObject responseObject = new JSONObject(uploadMediaResponse.body()); JSONObject responseObject = new JSONObject(uploadMediaResponse.body());
String fileName = responseObject.getJSONArray("files").getJSONObject(0).getString("file"); String fileName = responseObject.getJSONArray("files").getJSONObject(0).getString("file");
return mRetrofit.getBaseURL() + "/pictrs/image/" + fileName; String baseURL = mRetrofit.getBaseURL();
if (baseURL.endsWith("/")) {
baseURL = baseURL.substring(0, baseURL.length() - 1);
}
return baseURL + "/pictrs/image/" + fileName;
} else { } else {
return "Error: " + uploadMediaResponse.code(); return "Error: " + uploadMediaResponse.code();
} }

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,153 @@
package eu.toldi.infinityforlemmy.privatemessage
import eu.toldi.infinityforlemmy.RetrofitHolder
import eu.toldi.infinityforlemmy.apis.LemmyAPI
import eu.toldi.infinityforlemmy.dto.PrivateMessageDTO
import eu.toldi.infinityforlemmy.dto.PrivateMessageReadDTO
import eu.toldi.infinityforlemmy.utils.LemmyUtils
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()
}
}
)
}
fun sendPrivateMessage(
auth: String,
recipientId: Int,
content: String,
listener: PrivateMessageSentListener
) {
val api = retrofitHolder.retrofit.create(LemmyAPI::class.java)
api.privateMessageSend(PrivateMessageDTO(recipientId, content, auth)).enqueue(
object : retrofit2.Callback<String> {
override fun onResponse(
call: retrofit2.Call<String>,
response: retrofit2.Response<String>
) {
if (response.isSuccessful) {
listener.onPrivateMessageSentSuccess(
parsePrivateMessage(
JSONObject(response.body()!!).getJSONObject(
"private_message_view"
)
)
)
} else {
listener.onPrivateMessageSentError()
}
}
override fun onFailure(call: retrofit2.Call<String>, t: Throwable) {
listener.onPrivateMessageSentError()
}
}
)
}
interface PrivateMessageSentListener {
fun onPrivateMessageSentSuccess(privateMessage: PrivateMessage)
fun onPrivateMessageSentError()
}
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")
val updated = privateMessage.optString("updated", "")
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 = LemmyUtils.dateStringToMills(privateMessage.getString("published")),
updated = if (updated == "") {
null
} else {
LemmyUtils.dateStringToMills(updated)
},
creatorName = creator.getString("name"),
creatorAvatar = creator.optString("avatar", ""),
creatorQualifiedName = LemmyUtils.actorID2FullName(creator.getString("actor_id")),
recipientName = recipient.getString("name"),
recipientAvatar = recipient.optString("avatar", ""),
recipientQualifiedName = LemmyUtils.actorID2FullName(recipient.getString("actor_id"))
)
}
}

View File

@ -0,0 +1,76 @@
package eu.toldi.infinityforlemmy.privatemessage
import android.os.Parcel
import android.os.Parcelable
data class PrivateMessage(
val id: Int,
val creatorId: Int,
val recipientId: Int,
val content: String,
val deleted: Boolean,
var read: Boolean,
val published: Long,
val updated: Long?,
val creatorName: String,
val creatorAvatar: String,
val creatorQualifiedName: String,
val recipientName: String,
val recipientAvatar: String,
val recipientQualifiedName: String
) : Parcelable {
fun addReply(reply: PrivateMessage) {
replies.add(reply)
}
val replies = mutableListOf<PrivateMessage>()
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readString()!!,
parcel.readByte() != 0.toByte(),
parcel.readByte() != 0.toByte(),
parcel.readLong(),
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeInt(creatorId)
parcel.writeInt(recipientId)
parcel.writeString(content)
parcel.writeByte(if (deleted) 1 else 0)
parcel.writeByte(if (read) 1 else 0)
parcel.writeLong(published)
parcel.writeValue(updated)
parcel.writeString(creatorName)
parcel.writeString(creatorAvatar)
parcel.writeString(creatorQualifiedName)
parcel.writeString(recipientName)
parcel.writeString(recipientAvatar)
parcel.writeString(recipientQualifiedName)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<PrivateMessage> {
override fun createFromParcel(parcel: Parcel): PrivateMessage {
return PrivateMessage(parcel)
}
override fun newArray(size: Int): Array<PrivateMessage?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,78 @@
package eu.toldi.infinityforlemmy.privatemessage
import androidx.lifecycle.MutableLiveData
import androidx.paging.PageKeyedDataSource
import eu.toldi.infinityforlemmy.NetworkState
class PrivateMessageDataSource(
private val mLemmyPrivateMessageAPI: LemmyPrivateMessageAPI,
private val accessToken: String
) : PageKeyedDataSource<Int, PrivateMessage>() {
val initialLoadStateLiveData = MutableLiveData<NetworkState>()
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, PrivateMessage>
) {
mLemmyPrivateMessageAPI.fetchPrivateMessages(accessToken, 1, object :
LemmyPrivateMessageAPI.PrivateMessageFetchedListener {
override fun onPrivateMessageFetchedSuccess(privateMessages: List<PrivateMessage>) {
initialLoadStateLiveData.postValue(NetworkState.LOADED)
if (privateMessages.isEmpty()) {
callback.onResult(ArrayList(), null, null)
} else {
callback.onResult(privateMessages, null, 2)
}
}
override fun onPrivateMessageFetchedError() {
initialLoadStateLiveData.postValue(
NetworkState(
NetworkState.Status.FAILED,
"Error fetching messages"
)
)
}
})
}
override fun loadAfter(
params: LoadParams<Int>,
callback: LoadCallback<Int, PrivateMessage>
) {
mLemmyPrivateMessageAPI.fetchPrivateMessages(accessToken, params.key, object :
LemmyPrivateMessageAPI.PrivateMessageFetchedListener {
override fun onPrivateMessageFetchedSuccess(privateMessages: List<PrivateMessage>) {
initialLoadStateLiveData.postValue(NetworkState.LOADED)
if (privateMessages.isEmpty()) {
callback.onResult(ArrayList(), null)
} else {
callback.onResult(privateMessages, params.key + 1)
}
}
override fun onPrivateMessageFetchedError() {
initialLoadStateLiveData.postValue(
NetworkState(
NetworkState.Status.FAILED,
"Error fetching messages"
)
)
}
})
}
override fun loadBefore(
params: LoadParams<Int>,
callback: LoadCallback<Int, PrivateMessage>
) {
}
fun refresh() {
invalidate()
}
}

View File

@ -0,0 +1,20 @@
package eu.toldi.infinityforlemmy.privatemessage
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
class PrivateMessageDataSourceFactory(
private val mLemmyPrivateMessageAPI: LemmyPrivateMessageAPI,
private val accessToken: String
) : DataSource.Factory<Int, PrivateMessage>() {
val dataSourceLiveData = MutableLiveData<PrivateMessageDataSource>()
override fun create(): DataSource<Int, PrivateMessage> {
val dataSource = PrivateMessageDataSource(mLemmyPrivateMessageAPI, accessToken)
dataSourceLiveData.postValue(dataSource)
return dataSource
}
}

View File

@ -0,0 +1,52 @@
package eu.toldi.infinityforlemmy.privatemessage
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import retrofit2.Retrofit
import java.util.Locale
class PrivateMessageViewModel(
private val mLemmyPrivateMessageAPI: LemmyPrivateMessageAPI,
private val accessToken: String
) : ViewModel() {
private val dataSourceFactory =
PrivateMessageDataSourceFactory(mLemmyPrivateMessageAPI, accessToken)
val privateMessages = LivePagedListBuilder(
dataSourceFactory, PagedList.Config.Builder()
.setPageSize(20)
.setEnablePlaceholders(false)
.build()
)
.build()
val initialLoadState = Transformations.switchMap(dataSourceFactory.dataSourceLiveData) {
it.initialLoadStateLiveData
}
fun refresh() {
dataSourceFactory.dataSourceLiveData.value?.refresh()
}
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(
lemmyPrivateMessageAPI,
accessToken,
) as T
}
}
}

View File

@ -23,77 +23,75 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:orientation="vertical"> android:layout_weight="1">
<EditText <LinearLayout
android:id="@+id/username_edit_text_send_private_message_activity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:orientation="vertical">
android:gravity="top"
android:hint="@string/send_message_username_hint"
android:inputType="textCapSentences|textMultiLine"
android:paddingTop="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="?attr/primaryTextColor"
android:textSize="?attr/content_font_18"
android:fontFamily="?attr/content_font_family" />
<View <EditText
android:id="@+id/divider_1_send_private_message_activity" android:id="@+id/username_edit_text_send_private_message_activity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="match_parent"
android:layout_marginBottom="16dp" /> android:background="#00000000"
android:fontFamily="?attr/content_font_family"
android:gravity="top"
android:hint="@string/send_message_username_hint"
android:inputType="textCapSentences|textMultiLine"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="?attr/primaryTextColor"
android:textSize="?attr/content_font_18" />
<EditText <View
android:id="@+id/subjet_edit_text_send_private_message_activity" android:id="@+id/divider_1_send_private_message_activity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="1dp"
android:background="#00000000" android:layout_marginBottom="16dp" />
android:gravity="top"
android:hint="@string/send_message_subject_hint"
android:inputType="textCapSentences|textMultiLine"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textColor="?attr/primaryTextColor"
android:textSize="?attr/content_font_18"
android:maxLength="100"
android:fontFamily="?attr/content_font_family" />
<View <View
android:id="@+id/divider_2_send_private_message_activity" android:id="@+id/divider_2_send_private_message_activity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginBottom="16dp" /> android:layout_marginBottom="16dp" />
<EditText <EditText
android:id="@+id/content_edit_text_send_private_message_activity" android:id="@+id/content_edit_text_send_private_message_activity"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#00000000" android:background="#00000000"
android:gravity="top" android:fontFamily="?attr/content_font_family"
android:hint="@string/send_message_content_hint" android:gravity="top"
android:inputType="textCapSentences|textMultiLine" android:hint="@string/send_message_content_hint"
android:paddingStart="16dp" android:inputType="textCapSentences|textMultiLine"
android:paddingEnd="16dp" android:paddingStart="16dp"
android:paddingBottom="16dp" android:paddingEnd="16dp"
android:textColor="?attr/primaryTextColor" android:paddingBottom="16dp"
android:textSize="?attr/content_font_18" android:textColor="?attr/primaryTextColor"
android:fontFamily="?attr/content_font_family" /> android:textSize="?attr/content_font_18" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/markdown_bottom_bar_recycler_view_send_private_message_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="horizontal"
android:layout_gravity="bottom" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>