diff --git a/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java b/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java index 5af65c456..92bad7d67 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java +++ b/app/src/main/java/app/fedilab/android/mastodon/activities/DirectMessageActivity.java @@ -16,8 +16,12 @@ package app.fedilab.android.mastodon.activities; import static app.fedilab.android.BaseMainActivity.currentAccount; +import static app.fedilab.android.mastodon.activities.ComposeActivity.PICK_MEDIA; +import android.content.ClipData; +import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -25,10 +29,12 @@ import android.util.TypedValue; import android.view.MenuItem; import androidx.appcompat.app.ActionBar; -import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; +import java.util.ArrayList; +import java.util.List; + import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.databinding.ActivityDirectMessageBinding; @@ -46,7 +52,7 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto public static boolean expand; public static boolean displayCW; - Fragment currentFragment; + FragmentMastodonDirectMessage currentFragment; private Status firstMessage; private String remote_instance; @@ -88,7 +94,7 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto bundle.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance); FragmentMastodonDirectMessage FragmentMastodonDirectMessage = new FragmentMastodonDirectMessage(); FragmentMastodonDirectMessage.firstMessage = this; - currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, FragmentMastodonDirectMessage, bundle, null, null); + currentFragment = (FragmentMastodonDirectMessage) Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, FragmentMastodonDirectMessage, bundle, null, null); StatusesVM timelinesVM = new ViewModelProvider(DirectMessageActivity.this).get(StatusesVM.class); timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(DirectMessageActivity.this, status -> { if (status != null) { @@ -114,6 +120,24 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + List uris = new ArrayList<>(); + if (requestCode >= PICK_MEDIA && resultCode == RESULT_OK) { + ClipData clipData = data.getClipData(); + if (clipData != null) { + for (int i = 0; i < clipData.getItemCount(); i++) { + ClipData.Item item = clipData.getItemAt(i); + uris.add(item.getUri()); + } + } else { + uris.add(data.getData()); + } + currentFragment.addAttachment(uris); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ComposeAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ComposeAdapter.java index cbc4d9520..5cec5fac5 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ComposeAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ComposeAdapter.java @@ -137,7 +137,7 @@ import es.dmoral.toasty.Toasty; public class ComposeAdapter extends RecyclerView.Adapter { private static final int searchDeep = 15; - private static final int TYPE_COMPOSE = 1; + public static final int TYPE_COMPOSE = 1; public static boolean autocomplete = false; public static String[] ALPHA = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "!", ",", "?", diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java index 3192d2863..29cb0df8f 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/ConversationAdapter.java @@ -211,7 +211,7 @@ public class ConversationAdapter extends RecyclerView.Adapter { Intent intent; if (chatMode) { diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java index ab2947c55..1659e6e3b 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java @@ -54,6 +54,7 @@ import app.fedilab.android.mastodon.activities.MediaActivity; import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.helper.LongClickLinkMovementMethod; import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MediaHelper; import app.fedilab.android.mastodon.helper.ThemeHelper; @@ -213,6 +214,7 @@ public class StatusDirectMessageAdapter extends RecyclerView.Adapter(holder.binding.messageContent), () -> mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))), TextView.BufferType.SPANNABLE); + holder.binding.messageContent.setMovementMethod(LongClickLinkMovementMethod.getInstance()); if (measuredWidth <= 0 && status.media_attachments != null && status.media_attachments.size() > 0) { holder.binding.media.mediaContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override @@ -227,13 +229,19 @@ public class StatusDirectMessageAdapter extends RecyclerView.Adapter. */ +import static app.fedilab.android.BaseMainActivity.currentAccount; +import static app.fedilab.android.BaseMainActivity.currentInstance; +import static app.fedilab.android.BaseMainActivity.currentToken; +import static app.fedilab.android.BaseMainActivity.currentUserID; +import static app.fedilab.android.BaseMainActivity.instanceInfo; +import static app.fedilab.android.mastodon.activities.ComposeActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.text.InputFilter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.work.Data; +import androidx.work.OneTimeWorkRequest; +import androidx.work.OutOfQuotaPolicy; +import androidx.work.WorkManager; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.databinding.ComposeAttachmentItemBinding; import app.fedilab.android.databinding.FragmentDirectMessageBinding; +import app.fedilab.android.databinding.PopupMediaDescriptionBinding; +import app.fedilab.android.mastodon.activities.ComposeActivity; +import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Context; +import app.fedilab.android.mastodon.client.entities.api.Mention; import app.fedilab.android.mastodon.client.entities.api.Status; +import app.fedilab.android.mastodon.client.entities.app.StatusDraft; +import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.mastodon.helper.ThemeHelper; +import app.fedilab.android.mastodon.imageeditor.EditImageActivity; +import app.fedilab.android.mastodon.jobs.ComposeWorker; +import app.fedilab.android.mastodon.services.ThreadMessageService; import app.fedilab.android.mastodon.ui.drawer.StatusDirectMessageAdapter; import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM; @@ -51,6 +100,24 @@ public class FragmentMastodonDirectMessage extends Fragment { private boolean pullToRefresh; private String user_token, user_instance; + private final BroadcastReceiver broadcast_data = new BroadcastReceiver() { + @Override + public void onReceive(android.content.Context context, Intent intent) { + Bundle b = intent.getExtras(); + if (b != null) { + + if (b.getBoolean(Helper.RECEIVE_NEW_MESSAGE, false)) { + Status statusReceived = (Status) b.getSerializable(Helper.RECEIVE_STATUS_ACTION); + if (statusReceived != null) { + statuses.add(statusReceived); + statusDirectMessageAdapter.notifyItemInserted(statuses.size() - 1); + initiliazeStatus(); + } + } + } + } + }; + private Status statusCompose; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -88,10 +155,501 @@ public class FragmentMastodonDirectMessage extends Fragment { statusesVM.getContext(user_instance, user_token, focusedStatus.id) .observe(getViewLifecycleOwner(), this::initializeContextView); } + + statusCompose = new Status(); + binding.buttonAttach.setOnClickListener(v -> { + + if (instanceInfo.configuration.media_attachments.supported_mime_types != null) { + if (instanceInfo.getMimeTypeAudio().size() == 0) { + binding.buttonAttachAudio.setEnabled(false); + } + if (instanceInfo.getMimeTypeImage().size() == 0) { + binding.buttonAttachImage.setEnabled(false); + } + if (instanceInfo.getMimeTypeVideo().size() == 0) { + binding.buttonAttachVideo.setEnabled(false); + } + if (instanceInfo.getMimeTypeOther().size() == 0) { + binding.buttonAttachManual.setEnabled(false); + } + } + binding.attachmentChoicesPanel.setVisibility(View.VISIBLE); + binding.buttonAttach.setChecked(false); + }); + binding.buttonAttachAudio.setOnClickListener(v -> { + binding.attachmentChoicesPanel.setVisibility(View.GONE); + pickupMedia(ComposeActivity.mediaType.AUDIO); + }); + binding.buttonAttachImage.setOnClickListener(v -> { + binding.attachmentChoicesPanel.setVisibility(View.GONE); + pickupMedia(ComposeActivity.mediaType.PHOTO); + }); + binding.buttonAttachVideo.setOnClickListener(v -> { + binding.attachmentChoicesPanel.setVisibility(View.GONE); + pickupMedia(ComposeActivity.mediaType.VIDEO); + }); + binding.buttonAttachManual.setOnClickListener(v -> { + binding.attachmentChoicesPanel.setVisibility(View.GONE); + pickupMedia(ComposeActivity.mediaType.ALL); + }); + + binding.sendButton.setOnClickListener(v -> { + statusCompose.submitted = true; + onSubmit(prepareDraft(statusCompose, MainActivity.currentInstance, MainActivity.currentUserID)); + }); + LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); + + binding.text.setKeyBoardInputCallbackListener((inputContentInfo, flags, opts) -> { + if (inputContentInfo != null) { + Uri uri = inputContentInfo.getContentUri(); + List uris = new ArrayList<>(); + uris.add(uri); + addAttachment(uris); + } + }); + return binding.getRoot(); } + private void onSubmit(StatusDraft statusDraft) { + new Thread(() -> { + if (statusDraft.instance == null) { + statusDraft.instance = currentInstance; + } + if (statusDraft.user_id == null) { + statusDraft.user_id = currentUserID; + } + + if (!canBeSent(statusDraft)) { + return; + } + if (statusDraft.id > 0) { + try { + new StatusDraft(requireActivity()).updateStatusDraft(statusDraft); + } catch (DBException e) { + e.printStackTrace(); + } + } else { + try { + statusDraft.id = new StatusDraft(requireActivity()).insertStatusDraft(statusDraft); + } catch (DBException e) { + e.printStackTrace(); + } + } + int mediaCount = 0; + for (Status status : statusDraft.statusDraftList) { + mediaCount += status.media_attachments != null ? status.media_attachments.size() : 0; + } + if (mediaCount > 0) { + Data inputData = new Data.Builder() + .putString(Helper.ARG_STATUS_DRAFT_ID, String.valueOf(statusDraft.id)) + .putString(Helper.ARG_INSTANCE, currentInstance) + .putString(Helper.ARG_TOKEN, currentToken) + .putString(Helper.ARG_USER_ID, currentUserID) + .build(); + OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ComposeWorker.class) + .setInputData(inputData) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + .build(); + WorkManager.getInstance(requireActivity()).enqueue(request); + + } else { + new ThreadMessageService(requireActivity(), currentInstance, currentUserID, currentToken, statusDraft, null, null); + } + }).start(); + } + + + @Override + public void onDestroyView() { + LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(broadcast_data); + super.onDestroyView(); + } + + + private boolean canBeSent(StatusDraft statusDraft) { + if (statusDraft == null) { + return false; + } + List statuses = statusDraft.statusDraftList; + if (statuses == null || statuses.size() == 0) { + return false; + } + Status statusCheck = statuses.get(0); + if (statusCheck == null) { + return false; + } + return (statusCheck.text != null && statusCheck.text.trim().length() != 0) + || (statusCheck.media_attachments != null && statusCheck.media_attachments.size() != 0) + || statusCheck.poll != null + || (statusCheck.spoiler_text != null && statusCheck.spoiler_text.trim().length() != 0); + } + + public StatusDraft prepareDraft(Status status, String instance, String user_id) { + //Collect all statusCompose + List statusDrafts = new ArrayList<>(); + statusDrafts.add(status); + StatusDraft statusDraftDB = new StatusDraft(); + statusDraftDB.statusReplyList = new ArrayList<>(); + statusDraftDB.statusReplyList.addAll(statuses); + statusDraftDB.statusDraftList = new ArrayList<>(); + statusDraftDB.statusDraftList.addAll(statusDrafts); + statusDraftDB.instance = instance; + statusDraftDB.user_id = user_id; + return statusDraftDB; + } + + private Status initiliazeStatus() { + Status status = new Status(); + if (statuses != null && statuses.size() > 0) { + Status lastStatus = statuses.get(statuses.size() - 1); + status.in_reply_to_id = lastStatus.id; + status.visibility = "direct"; + status.mentions = new ArrayList<>(); + if (lastStatus.account.acct != null && currentAccount.mastodon_account != null && !lastStatus.account.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) { + Mention mention = new Mention(); + mention.acct = "@" + lastStatus.account.acct; + mention.url = lastStatus.account.url; + mention.username = lastStatus.account.username; + status.mentions.add(mention); + } + //There are other mentions to + if (lastStatus.mentions != null && lastStatus.mentions.size() > 0) { + for (Mention mentionTmp : lastStatus.mentions) { + if (currentAccount.mastodon_account != null && !mentionTmp.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) { + status.mentions.add(mentionTmp); + } + } + } + } + return status; + } + + /** + * Manage mentions displayed when replying to a message + * + * @param statusCompose {@link Status} - Status that user is replying + */ + private void manageMentions(Status statusCompose) { + + if (statusCompose.mentions != null && (statusCompose.text == null || statusCompose.text.length() == 0) && statusCompose.mentions.size() > 0) { + //Retrieves mentioned accounts + OP and adds them at the beginin of the toot + final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + Mention inReplyToUser; + inReplyToUser = statusCompose.mentions.get(0); + if (statusCompose.text == null) { + statusCompose.text = ""; + } + //Put other accounts mentioned at the bottom + boolean capitalize = sharedpreferences.getBoolean(getString(R.string.SET_CAPITALIZE), true); + boolean mentionsAtTop = sharedpreferences.getBoolean(getString(R.string.SET_MENTIONS_AT_TOP), false); + + if (inReplyToUser != null) { + if (capitalize && !mentionsAtTop) { + statusCompose.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + "\n" : "@" + inReplyToUser.acct + "\n"; + } else { + statusCompose.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + " " : "@" + inReplyToUser.acct + " "; + } + } + binding.text.setText(statusCompose.text); + statusCompose.cursorPosition = statusCompose.text.length(); + if (statusCompose.mentions.size() > 1) { + if (!mentionsAtTop) { + statusCompose.text += "\n"; + } + for (int i = 1; i < statusCompose.mentions.size(); i++) { + String tootTemp = String.format("@%s ", statusCompose.mentions.get(i).acct); + statusCompose.text = String.format("%s ", (statusCompose.text + tootTemp.trim())); + } + } + binding.text.setText(statusCompose.text); + binding.text.requestFocus(); + binding.text.post(() -> { + binding.text.setSelection(statusCompose.cursorPosition); //Put cursor at the end + }); + } else { + binding.text.requestFocus(); + } + + } + + /** + * Manage the actions when picking up a media + * + * @param type - type of media in the list of {@link ComposeActivity.mediaType} + */ + private void pickupMedia(ComposeActivity.mediaType type) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) != + PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + return; + } + } + Intent intent; + intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + String[] mimetypes = new String[0]; + if (type == ComposeActivity.mediaType.PHOTO) { + if (instanceInfo != null && instanceInfo.getMimeTypeImage() != null && instanceInfo.getMimeTypeImage().size() > 0) { + mimetypes = instanceInfo.getMimeTypeImage().toArray(new String[0]); + } else { + mimetypes = new String[]{"image/*"}; + } + } else if (type == ComposeActivity.mediaType.VIDEO) { + if (instanceInfo != null && instanceInfo.getMimeTypeVideo() != null && instanceInfo.getMimeTypeVideo().size() > 0) { + mimetypes = instanceInfo.getMimeTypeVideo().toArray(new String[0]); + } else { + mimetypes = new String[]{"video/*"}; + } + } else if (type == ComposeActivity.mediaType.AUDIO) { + if (instanceInfo != null && instanceInfo.getMimeTypeAudio() != null && instanceInfo.getMimeTypeAudio().size() > 0) { + mimetypes = instanceInfo.getMimeTypeAudio().toArray(new String[0]); + } else { + mimetypes = new String[]{"audio/mpeg", "audio/opus", "audio/flac", "audio/wav", "audio/ogg"}; + } + } else if (type == ComposeActivity.mediaType.ALL) { + if (instanceInfo != null && instanceInfo.getMimeTypeOther() != null && instanceInfo.getMimeTypeOther().size() > 0) { + mimetypes = instanceInfo.getMimeTypeOther().toArray(new String[0]); + } else { + mimetypes = new String[]{"*/*"}; + } + } + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + requireActivity().startActivityForResult(intent, (ComposeActivity.PICK_MEDIA)); + } + + + /** + * Add an attachment from ComposeActivity + * + * @param uris List - uris of the media + */ + public void addAttachment(List uris) { + Helper.createAttachmentFromUri(requireActivity(), uris, attachments -> { + if (statusCompose.media_attachments == null) { + statusCompose.media_attachments = new ArrayList<>(); + } + statusCompose.media_attachments.addAll(attachments); + if (statusCompose.media_attachments.size() > 0) { + displayAttachments(statusCompose.media_attachments.size() - 1); + } + }); + } + + /** + * Display attachment for a holder + */ + private void displayAttachments(int scrollToMediaPosition) { + if (statusCompose != null && statusCompose.media_attachments != null) { + binding.attachmentsList.removeAllViews(); + List attachmentList = statusCompose.media_attachments; + if (attachmentList != null && attachmentList.size() > 0) { + int mediaPosition = 0; + for (Attachment attachment : attachmentList) { + ComposeAttachmentItemBinding composeAttachmentItemBinding = ComposeAttachmentItemBinding.inflate(LayoutInflater.from(requireActivity()), binding.attachmentsList, false); + composeAttachmentItemBinding.buttonPlay.setVisibility(View.GONE); + /* if (editMessageId != null && attachment.url != null) { + composeAttachmentItemBinding.editPreview.setVisibility(View.GONE); + composeAttachmentItemBinding.buttonDescription.setVisibility(View.INVISIBLE); + composeAttachmentItemBinding.buttonOrderDown.setVisibility(View.INVISIBLE); + composeAttachmentItemBinding.buttonOrderUp.setVisibility(View.INVISIBLE); + }*/ + String attachmentPath = attachment.local_path != null && !attachment.local_path.trim().isEmpty() ? attachment.local_path : attachment.preview_url; + if (attachment.type != null || attachment.mimeType != null) { + if ((attachment.type != null && attachment.type.toLowerCase().startsWith("image")) || (attachment.mimeType != null && attachment.mimeType.toLowerCase().startsWith("image"))) { + Glide.with(composeAttachmentItemBinding.preview.getContext()) + .load(attachmentPath) + //.diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(composeAttachmentItemBinding.preview); + } else if ((attachment.type != null && attachment.type.toLowerCase().startsWith("video")) || (attachment.mimeType != null && attachment.mimeType.toLowerCase().startsWith("video"))) { + composeAttachmentItemBinding.buttonPlay.setVisibility(View.VISIBLE); + long interval = 2000; + RequestOptions options = new RequestOptions().frame(interval); + Glide.with(composeAttachmentItemBinding.preview.getContext()).asBitmap() + .load(attachmentPath) + .apply(options) + .into(composeAttachmentItemBinding.preview); + } else if ((attachment.type != null && attachment.type.toLowerCase().startsWith("audio")) || (attachment.mimeType != null && attachment.mimeType.toLowerCase().startsWith("audio"))) { + Glide.with(composeAttachmentItemBinding.preview.getContext()) + .load(R.drawable.ic_baseline_audio_file_24) + .into(composeAttachmentItemBinding.preview); + } else { + Glide.with(composeAttachmentItemBinding.preview.getContext()) + .load(R.drawable.ic_baseline_insert_drive_file_24) + .into(composeAttachmentItemBinding.preview); + } + } else { + Glide.with(composeAttachmentItemBinding.preview.getContext()) + .load(R.drawable.ic_baseline_insert_drive_file_24) + .into(composeAttachmentItemBinding.preview); + } + if (mediaPosition == 0) { + composeAttachmentItemBinding.buttonOrderUp.setVisibility(View.INVISIBLE); + } else { + composeAttachmentItemBinding.buttonOrderUp.setVisibility(View.VISIBLE); + } + if (mediaPosition == attachmentList.size() - 1) { + composeAttachmentItemBinding.buttonOrderDown.setVisibility(View.INVISIBLE); + } else { + composeAttachmentItemBinding.buttonOrderDown.setVisibility(View.VISIBLE); + } + //Remote attachments when deleting/redrafting can't be ordered + if (attachment.local_path == null) { + composeAttachmentItemBinding.buttonOrderUp.setVisibility(View.INVISIBLE); + composeAttachmentItemBinding.buttonOrderDown.setVisibility(View.INVISIBLE); + } + int finalMediaPosition = mediaPosition; + if (attachment.local_path != null && (attachment.local_path.endsWith("png") || attachment.local_path.endsWith("jpg") || attachment.local_path.endsWith("jpeg"))) { + composeAttachmentItemBinding.editPreview.setVisibility(View.VISIBLE); + } else { + composeAttachmentItemBinding.editPreview.setVisibility(View.GONE); + } + composeAttachmentItemBinding.editPreview.setOnClickListener(v -> { + Intent intent = new Intent(requireActivity(), EditImageActivity.class); + Bundle b = new Bundle(); + intent.putExtra("imageUri", attachment.local_path); + intent.putExtras(b); + startActivity(intent); + }); + composeAttachmentItemBinding.buttonDescription.setOnClickListener(v -> { + AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(requireActivity()); + // builderInner.setTitle(R.string.upload_form_description); + PopupMediaDescriptionBinding popupMediaDescriptionBinding = PopupMediaDescriptionBinding.inflate(LayoutInflater.from(requireActivity()), null, false); + builderInner.setView(popupMediaDescriptionBinding.getRoot()); + + popupMediaDescriptionBinding.mediaDescription.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1500)}); + popupMediaDescriptionBinding.mediaDescription.requestFocus(); + Glide.with(popupMediaDescriptionBinding.mediaPicture.getContext()) + .asBitmap() + .load(attachmentPath) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, Transition transition) { + popupMediaDescriptionBinding.mediaPicture.setImageBitmap(resource); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + + } + + @Override + public void onLoadFailed(@Nullable Drawable errorDrawable) { + super.onLoadFailed(errorDrawable); + } + }); + builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + if (attachment.description != null) { + popupMediaDescriptionBinding.mediaDescription.setText(attachment.description); + popupMediaDescriptionBinding.mediaDescription.setSelection(popupMediaDescriptionBinding.mediaDescription.getText().length()); + } + builderInner.setPositiveButton(R.string.validate, (dialog, which) -> { + attachment.description = popupMediaDescriptionBinding.mediaDescription.getText().toString(); + displayAttachments(finalMediaPosition); + dialog.dismiss(); + }); + AlertDialog alertDialog = builderInner.create(); + Objects.requireNonNull(alertDialog.getWindow()).setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + alertDialog.show(); + popupMediaDescriptionBinding.mediaDescription.requestFocus(); + + }); + + composeAttachmentItemBinding.buttonOrderUp.setOnClickListener(v -> { + if (finalMediaPosition > 0 && attachmentList.size() > 1) { + Attachment at1 = attachmentList.get(finalMediaPosition); + Attachment at2 = attachmentList.get(finalMediaPosition - 1); + attachmentList.set(finalMediaPosition - 1, at1); + attachmentList.set(finalMediaPosition, at2); + displayAttachments(finalMediaPosition - 1); + } + }); + composeAttachmentItemBinding.buttonOrderDown.setOnClickListener(v -> { + if (finalMediaPosition < (attachmentList.size() - 1) && attachmentList.size() > 1) { + Attachment at1 = attachmentList.get(finalMediaPosition); + Attachment at2 = attachmentList.get(finalMediaPosition + 1); + attachmentList.set(finalMediaPosition, at2); + attachmentList.set(finalMediaPosition + 1, at1); + displayAttachments(finalMediaPosition + 1); + } + }); + composeAttachmentItemBinding.buttonRemove.setOnClickListener(v -> { + AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(requireActivity()); + builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderInner.setPositiveButton(R.string.delete, (dialog, which) -> { + attachmentList.remove(attachment); + displayAttachments(finalMediaPosition); + new Thread(() -> { + if (attachment.local_path != null) { + File fileToDelete = new File(attachment.local_path); + if (fileToDelete.exists()) { + //noinspection ResultOfMethodCallIgnored + fileToDelete.delete(); + } + } + }).start(); + + }); + builderInner.setMessage(R.string.toot_delete_media); + builderInner.show(); + }); + composeAttachmentItemBinding.preview.setOnClickListener(v -> displayAttachments(finalMediaPosition)); + if (attachment.description == null || attachment.description.trim().isEmpty()) { + composeAttachmentItemBinding.buttonDescription.setChipIconResource(R.drawable.ic_baseline_warning_24); + composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(requireActivity(), R.color.black)); + composeAttachmentItemBinding.buttonDescription.setChipIconTintResource(R.color.black); + composeAttachmentItemBinding.buttonDescription.setChipBackgroundColor(ThemeHelper.getNoDescriptionColorStateList(requireActivity())); + } else { + composeAttachmentItemBinding.buttonDescription.setChipIconResource(R.drawable.ic_baseline_check_circle_24); + composeAttachmentItemBinding.buttonDescription.setTextColor(ContextCompat.getColor(requireActivity(), R.color.white)); + composeAttachmentItemBinding.buttonDescription.setChipIconTintResource(R.color.white); + composeAttachmentItemBinding.buttonDescription.setChipBackgroundColor(ThemeHelper.getHavingDescriptionColorStateList(requireActivity())); + } + binding.attachmentsList.addView(composeAttachmentItemBinding.getRoot()); + mediaPosition++; + } + binding.attachmentsList.setVisibility(View.VISIBLE); + if (scrollToMediaPosition >= 0 && binding.attachmentsList.getChildCount() < scrollToMediaPosition) { + binding.attachmentsList.requestChildFocus(binding.attachmentsList.getChildAt(scrollToMediaPosition), binding.attachmentsList.getChildAt(scrollToMediaPosition)); + } + } else { + binding.attachmentsList.setVisibility(View.GONE); + } + } else { + binding.attachmentsList.setVisibility(View.GONE); + } + buttonState(); + } + + + /** + * Manage state of media and poll button + */ + private void buttonState() { + if (BaseMainActivity.software == null || BaseMainActivity.software.toUpperCase().compareTo("MASTODON") == 0) { + if (statusCompose.poll == null) { + binding.buttonAttachImage.setEnabled(true); + binding.buttonAttachVideo.setEnabled(true); + binding.buttonAttachAudio.setEnabled(true); + binding.buttonAttachManual.setEnabled(true); + } else { + binding.buttonAttachImage.setEnabled(false); + binding.buttonAttachVideo.setEnabled(false); + binding.buttonAttachAudio.setEnabled(false); + binding.buttonAttachManual.setEnabled(false); + binding.buttonPoll.setEnabled(true); + } + binding.buttonPoll.setEnabled(statusCompose.media_attachments == null || statusCompose.media_attachments.size() <= 0); + } + } + /** * Intialize the common view for the context * @@ -127,12 +685,8 @@ public class FragmentMastodonDirectMessage extends Fragment { statuses.addAll(0, context.ancestors); statusDirectMessageAdapter.notifyItemRangeInserted(0, statusPosition); statuses.addAll(statusPosition + 1, context.descendants); + initiliazeStatus(); statusDirectMessageAdapter.notifyItemRangeInserted(statusPosition + 1, context.descendants.size()); - if (binding.recyclerView.getItemDecorationCount() > 0) { - for (int i = 0; i < binding.recyclerView.getItemDecorationCount(); i++) { - binding.recyclerView.removeItemDecorationAt(i); - } - } binding.swipeContainer.setRefreshing(false); binding.recyclerView.scrollToPosition(statusPosition); } diff --git a/app/src/main/res/layouts/mastodon/layout/activity_direct_message.xml b/app/src/main/res/layouts/mastodon/layout/activity_direct_message.xml index cf0e020f5..e6638f085 100644 --- a/app/src/main/res/layouts/mastodon/layout/activity_direct_message.xml +++ b/app/src/main/res/layouts/mastodon/layout/activity_direct_message.xml @@ -26,7 +26,7 @@ + tools:context=".mastodon.activities.DirectMessageActivity"> + android:fitsSystemWindows="true"> + app:defaultNavHost="true" /> \ No newline at end of file diff --git a/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml b/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml index bc8e98f59..f0cfe6726 100644 --- a/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml +++ b/app/src/main/res/layouts/mastodon/layout/drawer_status_chat.xml @@ -3,26 +3,26 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mainContainer" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/fab_margin" android:layout_marginTop="12dp" android:background="@drawable/bubble_right_tail" - android:padding="6dp" - android:paddingEnd="12dp"> + android:padding="12dp"> - + android:orientation="vertical"> - + android:baselineAligned="false" + android:orientation="horizontal"> - + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginHorizontal="6dp" + app:layout_constraintTop_toBottomOf="@id/sensitive_media"> - + + +