From 9f2ab52964dd4ad9192ce05be33dfbfe40ec48e0 Mon Sep 17 00:00:00 2001 From: nuclearfog Date: Wed, 19 Apr 2023 17:05:52 +0200 Subject: [PATCH] added status edit support --- .../twidda/backend/api/Connection.java | 3 +- .../twidda/backend/api/mastodon/Mastodon.java | 13 +++- .../backend/api/twitter/v1/TwitterV1.java | 4 +- .../twidda/backend/async/StatusUpdater.java | 16 ++-- .../twidda/backend/helper/StatusUpdate.java | 60 ++++++++++++--- .../twidda/config/Configuration.java | 10 +++ .../twidda/ui/activities/StatusActivity.java | 46 ++++++++--- .../twidda/ui/activities/StatusEditor.java | 76 ++++++++++++------- app/src/main/res/menu/status.xml | 5 ++ app/src/main/res/values/strings.xml | 1 + 10 files changed, 174 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java index 108257bc..b790fc09 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java @@ -417,8 +417,9 @@ public interface Connection { * * @param update status update information * @param mediaIds IDs of the uploaded media files if any + * @return uploaded status */ - void uploadStatus(StatusUpdate update, long[] mediaIds) throws ConnectionException; + Status uploadStatus(StatusUpdate update, long[] mediaIds) throws ConnectionException; /** * create userlist diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java index 3dad43f1..088c4398 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java @@ -593,7 +593,7 @@ public class Mastodon implements Connection { @Override - public void uploadStatus(StatusUpdate update, long[] mediaIds) throws MastodonException { + public Status uploadStatus(StatusUpdate update, long[] mediaIds) throws MastodonException { List params = new ArrayList<>(); // add identifier to prevent duplicate posts params.add("Idempotency-Key=" + System.currentTimeMillis() / 5000); @@ -625,10 +625,15 @@ public class Mastodon implements Connection { params.add("poll[hide_totals]=" + poll.hideTotalVotes()); } try { - Response response = post(ENDPOINT_STATUS, params); - if (response.code() != 200) { - throw new MastodonException(response); + Response response; + if (update.statusExists()) + response = put(ENDPOINT_STATUS + update.getStatusId(), params); + else + response = post(ENDPOINT_STATUS, params); + if (response.code() == 200) { + return createStatus(response); } + throw new MastodonException(response); } catch (IOException e) { throw new MastodonException(e); } diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java index 25730622..5d9fb141 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java @@ -722,7 +722,7 @@ public class TwitterV1 implements Connection { @Override - public void uploadStatus(StatusUpdate update, long[] mediaIds) throws TwitterException { + public Status uploadStatus(StatusUpdate update, long[] mediaIds) throws TwitterException { List params = new ArrayList<>(); if (update.getText() != null) params.add("status=" + StringUtils.encode(update.getText())); @@ -743,7 +743,7 @@ public class TwitterV1 implements Connection { params.add("lat=" + StringUtils.encode(lat)); params.add("long=" + StringUtils.encode(lon)); } - getTweet(TWEET_UPLOAD, params); + return getTweet(TWEET_UPLOAD, params); } diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java index ac11c5ea..ee035fd8 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java @@ -10,6 +10,7 @@ import org.nuclearfog.twidda.backend.api.ConnectionException; import org.nuclearfog.twidda.backend.api.ConnectionManager; import org.nuclearfog.twidda.backend.helper.MediaStatus; import org.nuclearfog.twidda.backend.helper.StatusUpdate; +import org.nuclearfog.twidda.model.Status; import org.nuclearfog.twidda.ui.activities.StatusEditor; /** @@ -41,16 +42,16 @@ public class StatusUpdater extends AsyncExecutor mediaUriStrings = new ArrayList<>(5); private Set supportedFormats = new TreeSet<>(); - private MediaStatus[] mediaUpdates = {}; private boolean attachmentLimitReached = false; - private boolean sensitive = false; - private boolean spoiler = false; - private int visibility = Status.VISIBLE_PUBLIC; - private int attachment = EMPTY; + + /** + * set existing status to edit + * + * @param status existing status + */ + public void setStatus(Status status) { + statusId = status.getId(); + replyId = status.getRepliedStatusId(); + text = status.getText(); + sensitive = status.isSensitive(); + spoiler = status.isSpoiler(); + visibility = status.getVisibility(); + } + + /** + * to edit an existing status, the ID can added + * + * @param statusId ID of an existing status to edit + */ + public void addStatusId(long statusId) { + this.statusId = statusId; + } /** * set ID of the replied status @@ -226,6 +252,22 @@ public class StatusUpdate implements Serializable { this.instance = instance; } + /** + * @return true to edit an existing status {@link #statusId} must be set + */ + public boolean statusExists() { + return statusId != 0L; + } + + /** + * get ID of an existing status to edit + * + * @return status ID or '0' to post a new status instead of edit + */ + public long getStatusId() { + return statusId; + } + /** * get ID of the replied status * diff --git a/app/src/main/java/org/nuclearfog/twidda/config/Configuration.java b/app/src/main/java/org/nuclearfog/twidda/config/Configuration.java index b19647b0..f03af9ee 100644 --- a/app/src/main/java/org/nuclearfog/twidda/config/Configuration.java +++ b/app/src/main/java/org/nuclearfog/twidda/config/Configuration.java @@ -46,6 +46,7 @@ public enum Configuration { private final boolean statusVisibilitySupported; private final boolean directMessageSupported; private final boolean emojiSupported; + private final boolean statusEditSupported; private final int arrayResHome; /** @@ -69,6 +70,7 @@ public enum Configuration { statusVisibilitySupported = false; directMessageSupported = true; emojiSupported = false; + statusEditSupported = false; arrayResHome = R.array.home_twitter_icons; break; @@ -87,6 +89,7 @@ public enum Configuration { statusVisibilitySupported = true; directMessageSupported = false; emojiSupported = true; + statusEditSupported = true; arrayResHome = R.array.home_mastodon_icons; break; } @@ -190,6 +193,13 @@ public enum Configuration { return emojiSupported; } + /** + * @return true if status edit is supported + */ + public boolean isStatusEditSupported() { + return statusEditSupported; + } + /** * get home tabitems drawable IDs * diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java index 487e9789..18234bf1 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java @@ -2,6 +2,7 @@ package org.nuclearfog.twidda.ui.activities; import static org.nuclearfog.twidda.ui.activities.SearchActivity.KEY_SEARCH_QUERY; import static org.nuclearfog.twidda.ui.activities.StatusEditor.KEY_STATUS_EDITOR_DATA; +import static org.nuclearfog.twidda.ui.activities.StatusEditor.KEY_STATUS_EDITOR_EDIT; import static org.nuclearfog.twidda.ui.activities.UsersActivity.KEY_USERS_ID; import static org.nuclearfog.twidda.ui.activities.UsersActivity.KEY_USERS_MODE; import static org.nuclearfog.twidda.ui.activities.UsersActivity.USERS_FAVORIT; @@ -33,6 +34,10 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -102,7 +107,7 @@ import jp.wasabeef.picasso.transformations.RoundedCornersTransformation; * @author nuclearfog */ public class StatusActivity extends AppCompatActivity implements OnClickListener, OnLongClickListener, OnTagClickListener, - OnConfirmListener, OnCardClickListener, OnScrollChangeListener, LockCallback { + OnConfirmListener, OnCardClickListener, OnScrollChangeListener, LockCallback, ActivityResultCallback { /** * Activity result code to update existing status information @@ -194,6 +199,7 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener */ private static final int MENU_GROUP_COPY = 0x157426; + private ActivityResultLauncher activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this); private AsyncCallback statusCallback = this::onStatusResult; private AsyncCallback pollResult = this::onPollResult; private AsyncCallback translationResult = this::onTranslationResult; @@ -439,6 +445,7 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener MenuItem optCopy = m.findItem(R.id.menu_status_copy); MenuItem optMetrics = m.findItem(R.id.menu_status_metrics); MenuItem menuBookmark = m.findItem(R.id.menu_status_bookmark); + MenuItem editStatus = m.findItem(R.id.menu_status_edit); SubMenu copyMenu = optCopy.getSubMenu(); // set status options @@ -462,6 +469,9 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener // enable/disable status hide option if (currentStatus.getAuthor().isCurrentUser()) { optDelete.setVisible(true); + if (settings.getLogin().getConfiguration().isStatusEditSupported()) { + editStatus.setVisible(true); + } } // enable/disable status metrics option if (currentStatus.getMetrics() != null) { @@ -496,7 +506,7 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener return true; } // add/remove bookmark - if (item.getItemId() == R.id.menu_status_bookmark) { + else if (item.getItemId() == R.id.menu_status_bookmark) { Toast.makeText(getApplicationContext(), R.string.info_loading, Toast.LENGTH_SHORT).show(); int mode = status.isBookmarked() ? StatusParam.UNBOOKMARK : StatusParam.BOOKMARK; StatusParam param = new StatusParam(mode, status.getId()); @@ -557,10 +567,28 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener } return true; } + // edit status + else if (item.getItemId() == R.id.menu_status_edit) { + Intent intent = new Intent(this, StatusEditor.class); + intent.putExtra(KEY_STATUS_EDITOR_DATA, status); + intent.putExtra(KEY_STATUS_EDITOR_EDIT, true); + activityResultLauncher.launch(intent); + } return super.onOptionsItemSelected(item); } + @Override + public void onActivityResult(ActivityResult result) { + if (result.getData() != null && result.getResultCode() == StatusEditor.RETURN_STATUS_UPDATE) { + Serializable data = result.getData().getSerializableExtra(StatusEditor.RETURN_STATUS_DATA); + if (data instanceof Status) { + setStatus((Status) data); + } + } + } + + @Override public void onClick(View v) { if (status != null) { @@ -835,14 +863,12 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener } else { statusApi.setVisibility(View.GONE); } - if (statusText.getText().length() == 0) { - if (!status.getText().isEmpty()) { - spannableText = Tagger.makeTextWithLinks(status.getText(), settings.getHighlightColor(), this); - statusText.setVisibility(View.VISIBLE); - statusText.setText(spannableText); - } else { - statusText.setVisibility(View.GONE); - } + if (!status.getText().isEmpty()) { + spannableText = Tagger.makeTextWithLinks(status.getText(), settings.getHighlightColor(), this); + statusText.setVisibility(View.VISIBLE); + statusText.setText(spannableText); + } else { + statusText.setVisibility(View.GONE); } if (status.getRepliedStatusId() > 0) { if (!status.getReplyName().isEmpty()) diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusEditor.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusEditor.java index 05bb2516..aa24d178 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusEditor.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusEditor.java @@ -55,18 +55,35 @@ import java.io.Serializable; public class StatusEditor extends MediaActivity implements OnClickListener, OnProgressStopListener, OnConfirmListener, OnMediaClickListener, TextWatcher, PollUpdateCallback, OnEmojiSelectListener { + /** + * return code used to send status information to calling activity + */ + public static final int RETURN_STATUS_UPDATE = 0x30220; + /** * key to add the status to reply * value type is {@link Status} */ public static final String KEY_STATUS_EDITOR_DATA = "status_data"; + /** + * key to edit an existing status + * value type is Boolean + */ + public static final String KEY_STATUS_EDITOR_EDIT = "status_edit"; + /** * key for the text added to the status if any * value type is String */ public static final String KEY_STATUS_EDITOR_TEXT = "status_text"; + /** + * key to return uploaded status information + * value type is {@link Status} + */ + public static final String RETURN_STATUS_DATA = "status_update"; + /** * key for status update to restore * value type is {@link StatusUpdate} @@ -103,8 +120,8 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr @Override - protected void onCreate(@Nullable Bundle b) { - super.onCreate(b); + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); setContentView(R.layout.popup_status); ViewGroup root = findViewById(R.id.popup_status_root); ImageView background = findViewById(R.id.popup_status_background); @@ -134,20 +151,32 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr if (!settings.getLogin().getConfiguration().isEmojiSupported()) { emojiButton.setVisibility(View.GONE); } - long replyId = 0L; - String prefix; - Serializable serializedStatus = getIntent().getSerializableExtra(KEY_STATUS_EDITOR_DATA); - if (serializedStatus instanceof Status) { - Status status = (Status) serializedStatus; - replyId = status.getId(); - statusUpdate.setVisibility(status.getVisibility()); - prefix = status.getUserMentions(); - } else { - prefix = getIntent().getStringExtra(KEY_STATUS_EDITOR_TEXT); - } - statusUpdate.addReplyStatusId(replyId); - if (prefix != null) { - statusText.append(prefix); + // fetch parameters + if (savedInstanceState == null) + savedInstanceState = getIntent().getExtras(); + if (savedInstanceState != null) { + Serializable serializedStatus = savedInstanceState.getSerializable(KEY_STATUS_EDITOR_DATA); + Serializable serializedStatusUpdate = savedInstanceState.getSerializable(KEY_STATUS_UPDATE); + boolean editStatus = savedInstanceState.getBoolean(KEY_STATUS_EDITOR_EDIT, false); + String prefix = savedInstanceState.getString(KEY_STATUS_EDITOR_TEXT); + if (serializedStatusUpdate instanceof StatusUpdate) { + statusUpdate = (StatusUpdate) serializedStatusUpdate; + } else if (serializedStatus instanceof Status) { + Status status = (Status) serializedStatus; + if (editStatus) { + statusUpdate.setStatus(status); + statusText.append(status.getText()); + } else { + statusUpdate.addStatusId(status.getId()); + statusUpdate.addReplyStatusId(status.getId()); + statusUpdate.setVisibility(status.getVisibility()); + statusUpdate.addText(status.getUserMentions()); + statusText.append(status.getUserMentions()); + } + } else { + statusUpdate.addText(prefix); + statusText.append(prefix); + } } adapter = new IconAdapter(settings, true); adapter.addOnMediaClickListener(this); @@ -192,16 +221,6 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr } - @Override - protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - Serializable serializedStatusUpdate = savedInstanceState.getSerializable(KEY_STATUS_UPDATE); - if (serializedStatusUpdate instanceof StatusUpdate) { - statusUpdate = (StatusUpdate) serializedStatusUpdate; - } - } - - @Override public void onBackPressed() { showClosingMsg(); @@ -399,7 +418,10 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr * called when the status was successfully updated */ private void onStatusUpdated(@NonNull StatusUpdateResult result) { - if (result.success) { + if (result.status != null) { + Intent intent = new Intent(); + intent.putExtra(RETURN_STATUS_DATA, result.status); + setResult(RETURN_STATUS_UPDATE, intent); Toast.makeText(getApplicationContext(), R.string.info_status_sent, Toast.LENGTH_LONG).show(); finish(); } else { diff --git a/app/src/main/res/menu/status.xml b/app/src/main/res/menu/status.xml index 1920150d..e2fd7601 100644 --- a/app/src/main/res/menu/status.xml +++ b/app/src/main/res/menu/status.xml @@ -34,6 +34,11 @@ android:title="@string/menu_status_hide" android:visible="false" /> + + remove bookmark unmute follow requested + edit delete hide Metrics