diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/EditStatus.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/EditStatus.java new file mode 100644 index 000000000..ecadaf2eb --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/EditStatus.java @@ -0,0 +1,11 @@ +package org.joinmastodon.android.api.requests.statuses; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; + +public class EditStatus extends MastodonAPIRequest{ + public EditStatus(CreateStatus.Request req, String id){ + super(HttpMethod.PUT, "/statuses/"+id, Status.class); + setRequestBody(req); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/events/StatusUpdatedEvent.java b/mastodon/src/main/java/org/joinmastodon/android/events/StatusUpdatedEvent.java new file mode 100644 index 000000000..f906d5095 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/events/StatusUpdatedEvent.java @@ -0,0 +1,11 @@ +package org.joinmastodon.android.events; + +import org.joinmastodon.android.model.Status; + +public class StatusUpdatedEvent{ + public Status status; + + public StatusUpdatedEvent(Status status){ + this.status=status; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index da9c960be..05c311997 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -53,17 +53,20 @@ import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.api.ProgressListener; import org.joinmastodon.android.api.requests.statuses.CreateStatus; +import org.joinmastodon.android.api.requests.statuses.EditStatus; import org.joinmastodon.android.api.requests.statuses.UploadAttachment; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent; +import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Mention; +import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.ui.ComposeAutocompleteViewController; @@ -174,6 +177,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr private Instance instance; private boolean attachmentsErrorShowing; + private Status editingStatus; + @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -185,6 +190,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr instanceDomain=session.domain; customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain); instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain); + if(getArguments().containsKey("editStatus")){ + editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus")); + } if(instance==null){ Nav.finish(this); return; @@ -296,6 +304,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } updatePollOptionHints(); pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr)); + }else if(savedInstanceState==null && editingStatus!=null && editingStatus.poll!=null){ + pollBtn.setSelected(true); + mediaBtn.setEnabled(false); + pollWrap.setVisibility(View.VISIBLE); + for(Poll.Option eopt:editingStatus.poll.options){ + DraftPollOption opt=createDraftPollOption(); + opt.edit.setText(eopt.title); + } + updatePollOptionHints(); + pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr)); }else{ pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=getResources().getQuantityString(R.plurals.x_days, 1, 1))); } @@ -308,6 +326,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){ spoilerEdit.setVisibility(View.VISIBLE); spoilerBtn.setSelected(true); + }else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){ + spoilerEdit.setVisibility(View.VISIBLE); + spoilerEdit.setText(editingStatus.spoilerText); + spoilerBtn.setSelected(true); } if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){ @@ -465,25 +487,46 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr replyText.setVisibility(View.GONE); } if(savedInstanceState==null){ - String prefilledText=getArguments().getString("prefilledText"); - if(!TextUtils.isEmpty(prefilledText)){ - mainEditText.setText(prefilledText); + if(editingStatus!=null){ + mainEditText.setText(initialText=HtmlParser.strip(editingStatus.content)); mainEditText.setSelection(mainEditText.length()); - initialText=prefilledText; - } - ArrayList mediaUris=getArguments().getParcelableArrayList("mediaAttachments"); - if(mediaUris!=null && !mediaUris.isEmpty()){ - for(Uri uri:mediaUris){ - addMediaAttachment(uri, null); + if(!editingStatus.mediaAttachments.isEmpty()){ + attachmentsView.setVisibility(View.VISIBLE); + for(Attachment att:editingStatus.mediaAttachments){ + DraftMediaAttachment da=new DraftMediaAttachment(); + da.serverAttachment=att; + da.description=att.description; + da.uri=Uri.parse(att.previewUrl); + attachmentsView.addView(createMediaAttachmentView(da)); + attachments.add(da); + } + pollBtn.setEnabled(false); + } + }else{ + String prefilledText=getArguments().getString("prefilledText"); + if(!TextUtils.isEmpty(prefilledText)){ + mainEditText.setText(prefilledText); + mainEditText.setSelection(mainEditText.length()); + initialText=prefilledText; + } + ArrayList mediaUris=getArguments().getParcelableArrayList("mediaAttachments"); + if(mediaUris!=null && !mediaUris.isEmpty()){ + for(Uri uri:mediaUris){ + addMediaAttachment(uri, null); + } } } } + + if(editingStatus!=null){ + updateCharCounter(); + } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ publishButton=new Button(getActivity()); - publishButton.setText(R.string.publish); + publishButton.setText(editingStatus==null ? R.string.publish : R.string.save); publishButton.setOnClickListener(this::onPublishClick); LinearLayout wrap=new LinearLayout(getActivity()); wrap.setOrientation(LinearLayout.HORIZONTAL); @@ -506,7 +549,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr wrap.addView(publishButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8)); wrap.setClipToPadding(false); - MenuItem item=menu.add(R.string.publish); + MenuItem item=menu.add(editingStatus==null ? R.string.publish : R.string.save); item.setActionView(wrap); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); updatePublishButtonState(); @@ -610,31 +653,43 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr sendProgress.setVisibility(View.VISIBLE); sendError.setVisibility(View.GONE); - new CreateStatus(req, uuid) - .setCallback(new Callback<>(){ - @Override - public void onSuccess(Status result){ - wm.removeView(sendingOverlay); - sendingOverlay=null; - E.post(new StatusCreatedEvent(result)); - if(replyTo!=null){ - replyTo.repliesCount++; - E.post(new StatusCountersUpdatedEvent(replyTo)); - } - Nav.finish(ComposeFragment.this); + Callback resCallback=new Callback<>(){ + @Override + public void onSuccess(Status result){ + wm.removeView(sendingOverlay); + sendingOverlay=null; + if(editingStatus==null){ + E.post(new StatusCreatedEvent(result)); + if(replyTo!=null){ + replyTo.repliesCount++; + E.post(new StatusCountersUpdatedEvent(replyTo)); } + }else{ + E.post(new StatusUpdatedEvent(result)); + } + Nav.finish(ComposeFragment.this); + } - @Override - public void onError(ErrorResponse error){ - wm.removeView(sendingOverlay); - sendingOverlay=null; - sendProgress.setVisibility(View.GONE); - sendError.setVisibility(View.VISIBLE); - publishButton.setEnabled(true); - error.showToast(getActivity()); - } - }) - .exec(accountID); + @Override + public void onError(ErrorResponse error){ + wm.removeView(sendingOverlay); + sendingOverlay=null; + sendProgress.setVisibility(View.GONE); + sendError.setVisibility(View.VISIBLE); + publishButton.setEnabled(true); + error.showToast(getActivity()); + } + }; + + if(editingStatus!=null){ + new EditStatus(req, editingStatus.id) + .setCallback(resCallback) + .exec(accountID); + }else{ + new CreateStatus(req, uuid) + .setCallback(resCallback) + .exec(accountID); + } } private boolean hasDraft(){ @@ -686,7 +741,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr private void confirmDiscardDraftAndFinish(){ new M3AlertDialogBuilder(getActivity()) - .setTitle(R.string.discard_draft) + .setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes) .setPositiveButton(R.string.discard, (dialog, which)->Nav.finish(this)) .setNegativeButton(R.string.cancel, null) .show(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java index 39878e68a..f7bfb8209 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java @@ -9,12 +9,14 @@ import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusDeletedEvent; +import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.parceler.Parcels; +import java.util.ArrayList; import java.util.List; import androidx.recyclerview.widget.RecyclerView; @@ -60,6 +62,59 @@ public abstract class StatusListFragment extends BaseStatusListFragment{ protected void onStatusCreated(StatusCreatedEvent ev){} + protected void onStatusUpdated(StatusUpdatedEvent ev){ + ArrayList statusesForDisplayItems=new ArrayList<>(); + for(int i=0;i postItems=displayItems.subList(start, i); + postItems.clear(); + postItems.addAll(buildDisplayItems(s)); + int oldSize=i-start, newSize=postItems.size(); + if(oldSize==newSize){ + adapter.notifyItemRangeChanged(start, newSize); + }else if(oldSize{ StatusListFragment.this.onStatusCreated(ev); } + @Subscribe + public void onStatusUpdated(StatusUpdatedEvent ev){ + StatusListFragment.this.onStatusUpdated(ev); + } + @Subscribe public void onPollUpdated(PollUpdatedEvent ev){ if(!ev.accountID.equals(accountID)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java index ec49a3ab9..22d0eda16 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java @@ -43,7 +43,7 @@ public class ThreadFragment extends StatusListFragment{ @Override protected List buildDisplayItems(Status s){ List items=super.buildDisplayItems(s); - if(s==mainStatus){ + if(s.id.equals(mainStatus.id)){ for(StatusDisplayItem item:items){ if(item instanceof TextStatusDisplayItem text) text.textSelectable=true; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 1dec009a3..511109f2b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -23,6 +23,7 @@ import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.fragments.ComposeFragment; import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; @@ -135,7 +136,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ optionsMenu.setOnMenuItemClickListener(menuItem->{ Account account=item.user; int id=menuItem.getItemId(); - if(id==R.id.delete){ + if(id==R.id.edit){ + Bundle args=new Bundle(); + args.putString("account", item.parentFragment.getAccountID()); + args.putParcelable("editStatus", Parcels.wrap(item.status)); + Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); + }else if(id==R.id.delete){ UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{}); }else if(id==R.id.mute){ UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{}); @@ -252,6 +258,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ Account account=item.user; Menu menu=optionsMenu.getMenu(); boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account); + menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost); menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost); menu.findItem(R.id.open_in_browser).setVisible(item.status!=null); MenuItem blockDomain=menu.findItem(R.id.block_domain); diff --git a/mastodon/src/main/res/menu/post.xml b/mastodon/src/main/res/menu/post.xml index 877bd3e26..ca14e2b70 100644 --- a/mastodon/src/main/res/menu/post.xml +++ b/mastodon/src/main/res/menu/post.xml @@ -1,5 +1,6 @@ + diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 1bb27a789..272801162 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -368,4 +368,6 @@ Marked sensitive Marked not sensitive Post edited + Edit + Discard changes? \ No newline at end of file