Implement redraft feature. (#1190)
* Implement "Delete and Edit" feature * Some changes to ComposeActivity Support for uploaded medias, sensitive option. Fix typo. Change names of some extra keys. * Use Glide instead of Picasso * Pass ArrayList instead of json * Change wording for re-draft * Fix test
This commit is contained in:
parent
49ede9183d
commit
60d6927af6
12 changed files with 178 additions and 63 deletions
|
@ -60,13 +60,32 @@ import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.Px;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||||
|
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.keylesspalace.tusky.adapter.EmojiAdapter;
|
|
||||||
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter;
|
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter;
|
||||||
|
import com.keylesspalace.tusky.adapter.EmojiAdapter;
|
||||||
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener;
|
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener;
|
||||||
import com.keylesspalace.tusky.db.AccountEntity;
|
import com.keylesspalace.tusky.db.AccountEntity;
|
||||||
import com.keylesspalace.tusky.db.AppDatabase;
|
import com.keylesspalace.tusky.db.AppDatabase;
|
||||||
|
@ -81,10 +100,10 @@ import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.network.MastodonApi;
|
import com.keylesspalace.tusky.network.MastodonApi;
|
||||||
import com.keylesspalace.tusky.network.ProgressRequestBody;
|
import com.keylesspalace.tusky.network.ProgressRequestBody;
|
||||||
import com.keylesspalace.tusky.service.SendTootService;
|
import com.keylesspalace.tusky.service.SendTootService;
|
||||||
|
import com.keylesspalace.tusky.util.ComposeTokenizer;
|
||||||
import com.keylesspalace.tusky.util.CountUpDownLatch;
|
import com.keylesspalace.tusky.util.CountUpDownLatch;
|
||||||
import com.keylesspalace.tusky.util.DownsizeImageTask;
|
import com.keylesspalace.tusky.util.DownsizeImageTask;
|
||||||
import com.keylesspalace.tusky.util.ListUtils;
|
import com.keylesspalace.tusky.util.ListUtils;
|
||||||
import com.keylesspalace.tusky.util.ComposeTokenizer;
|
|
||||||
import com.keylesspalace.tusky.util.SaveTootHelper;
|
import com.keylesspalace.tusky.util.SaveTootHelper;
|
||||||
import com.keylesspalace.tusky.util.SpanUtilsKt;
|
import com.keylesspalace.tusky.util.SpanUtilsKt;
|
||||||
import com.keylesspalace.tusky.util.StringUtils;
|
import com.keylesspalace.tusky.util.StringUtils;
|
||||||
|
@ -114,24 +133,6 @@ import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.Px;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
|
||||||
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
|
||||||
import androidx.lifecycle.Lifecycle;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.transition.TransitionManager;
|
|
||||||
import at.connyduck.sparkbutton.helpers.Utils;
|
import at.connyduck.sparkbutton.helpers.Utils;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.SingleObserver;
|
import io.reactivex.SingleObserver;
|
||||||
|
@ -171,16 +172,18 @@ public final class ComposeActivity
|
||||||
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
||||||
|
|
||||||
private static final String SAVED_TOOT_UID_EXTRA = "saved_toot_uid";
|
private static final String SAVED_TOOT_UID_EXTRA = "saved_toot_uid";
|
||||||
private static final String SAVED_TOOT_TEXT_EXTRA = "saved_toot_text";
|
private static final String TOOT_TEXT_EXTRA = "toot_text";
|
||||||
private static final String SAVED_JSON_URLS_EXTRA = "saved_json_urls";
|
private static final String SAVED_JSON_URLS_EXTRA = "saved_json_urls";
|
||||||
private static final String SAVED_JSON_DESCRIPTIONS_EXTRA = "saved_json_descriptions";
|
private static final String SAVED_JSON_DESCRIPTIONS_EXTRA = "saved_json_descriptions";
|
||||||
private static final String SAVED_TOOT_VISIBILITY_EXTRA = "saved_toot_visibility";
|
private static final String TOOT_VISIBILITY_EXTRA = "toot_visibility";
|
||||||
private static final String IN_REPLY_TO_ID_EXTRA = "in_reply_to_id";
|
private static final String IN_REPLY_TO_ID_EXTRA = "in_reply_to_id";
|
||||||
private static final String REPLY_VISIBILITY_EXTRA = "reply_visibilty";
|
private static final String REPLY_VISIBILITY_EXTRA = "reply_visibility";
|
||||||
private static final String CONTENT_WARNING_EXTRA = "content_warning";
|
private static final String CONTENT_WARNING_EXTRA = "content_warning";
|
||||||
private static final String MENTIONED_USERNAMES_EXTRA = "netnioned_usernames";
|
private static final String MENTIONED_USERNAMES_EXTRA = "mentioned_usernames";
|
||||||
private static final String REPLYING_STATUS_AUTHOR_USERNAME_EXTRA = "replying_author_nickname_extra";
|
private static final String REPLYING_STATUS_AUTHOR_USERNAME_EXTRA = "replying_author_nickname_extra";
|
||||||
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content";
|
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content";
|
||||||
|
private static final String MEDIA_ATTACHMENTS_EXTRA = "media_attachments";
|
||||||
|
private static final String SENSITIVE_EXTRA = "sensitive";
|
||||||
// Mastodon only counts URLs as this long in terms of status character limits
|
// Mastodon only counts URLs as this long in terms of status character limits
|
||||||
static final int MAXIMUM_URL_LENGTH = 23;
|
static final int MAXIMUM_URL_LENGTH = 23;
|
||||||
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
||||||
|
@ -409,6 +412,7 @@ public final class ComposeActivity
|
||||||
String[] mentionedUsernames = null;
|
String[] mentionedUsernames = null;
|
||||||
ArrayList<String> loadedDraftMediaUris = null;
|
ArrayList<String> loadedDraftMediaUris = null;
|
||||||
ArrayList<String> loadedDraftMediaDescriptions = null;
|
ArrayList<String> loadedDraftMediaDescriptions = null;
|
||||||
|
ArrayList<Attachment> mediaAttachments = null;
|
||||||
inReplyToId = null;
|
inReplyToId = null;
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
|
|
||||||
|
@ -432,14 +436,13 @@ public final class ComposeActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If come from SavedTootActivity
|
String tootText = intent.getStringExtra(TOOT_TEXT_EXTRA);
|
||||||
String savedTootText = intent.getStringExtra(SAVED_TOOT_TEXT_EXTRA);
|
if (!TextUtils.isEmpty(tootText)) {
|
||||||
if (!TextUtils.isEmpty(savedTootText)) {
|
textEditor.setText(tootText);
|
||||||
startingText = savedTootText;
|
|
||||||
textEditor.setText(savedTootText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to redo a list of media
|
// try to redo a list of media
|
||||||
|
// If come from SavedTootActivity
|
||||||
String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA);
|
String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA);
|
||||||
String savedJsonDescriptions = intent.getStringExtra(SAVED_JSON_DESCRIPTIONS_EXTRA);
|
String savedJsonDescriptions = intent.getStringExtra(SAVED_JSON_DESCRIPTIONS_EXTRA);
|
||||||
if (!TextUtils.isEmpty(savedJsonUrls)) {
|
if (!TextUtils.isEmpty(savedJsonUrls)) {
|
||||||
|
@ -452,15 +455,20 @@ public final class ComposeActivity
|
||||||
new TypeToken<ArrayList<String>>() {
|
new TypeToken<ArrayList<String>>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
}
|
}
|
||||||
|
// If come from redraft
|
||||||
|
mediaAttachments = intent.getParcelableArrayListExtra(MEDIA_ATTACHMENTS_EXTRA);
|
||||||
|
|
||||||
int savedTootUid = intent.getIntExtra(SAVED_TOOT_UID_EXTRA, 0);
|
int savedTootUid = intent.getIntExtra(SAVED_TOOT_UID_EXTRA, 0);
|
||||||
if (savedTootUid != 0) {
|
if (savedTootUid != 0) {
|
||||||
this.savedTootUid = savedTootUid;
|
this.savedTootUid = savedTootUid;
|
||||||
|
|
||||||
|
// If come from SavedTootActivity
|
||||||
|
startingText = tootText;
|
||||||
}
|
}
|
||||||
|
|
||||||
int savedTootVisibility = intent.getIntExtra(SAVED_TOOT_VISIBILITY_EXTRA, Status.Visibility.UNKNOWN.getNum());
|
int tootVisibility = intent.getIntExtra(TOOT_VISIBILITY_EXTRA, Status.Visibility.UNKNOWN.getNum());
|
||||||
if (savedTootVisibility != Status.Visibility.UNKNOWN.getNum()) {
|
if (tootVisibility != Status.Visibility.UNKNOWN.getNum()) {
|
||||||
startingVisibility = Status.Visibility.byNum(savedTootVisibility);
|
startingVisibility = Status.Visibility.byNum(tootVisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent.hasExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA)) {
|
if (intent.hasExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA)) {
|
||||||
|
@ -491,6 +499,8 @@ public final class ComposeActivity
|
||||||
if (intent.hasExtra(REPLYING_STATUS_CONTENT_EXTRA)) {
|
if (intent.hasExtra(REPLYING_STATUS_CONTENT_EXTRA)) {
|
||||||
replyContentTextView.setText(intent.getStringExtra(REPLYING_STATUS_CONTENT_EXTRA));
|
replyContentTextView.setText(intent.getStringExtra(REPLYING_STATUS_CONTENT_EXTRA));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusMarkSensitive = intent.getBooleanExtra(SENSITIVE_EXTRA, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// After the starting state is finalised, the interface can be set to reflect this state.
|
// After the starting state is finalised, the interface can be set to reflect this state.
|
||||||
|
@ -575,6 +585,25 @@ public final class ComposeActivity
|
||||||
}
|
}
|
||||||
pickMedia(uri, mediaSize, description);
|
pickMedia(uri, mediaSize, description);
|
||||||
}
|
}
|
||||||
|
} else if (!ListUtils.isEmpty(mediaAttachments)) {
|
||||||
|
for (int mediaIndex =0; mediaIndex < mediaAttachments.size(); ++mediaIndex) {
|
||||||
|
Attachment media = mediaAttachments.get(mediaIndex);
|
||||||
|
QueuedMedia.Type type;
|
||||||
|
switch (media.getType()) {
|
||||||
|
case UNKNOWN:
|
||||||
|
case IMAGE:
|
||||||
|
default: {
|
||||||
|
type = QueuedMedia.Type.IMAGE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VIDEO:
|
||||||
|
case GIFV: {
|
||||||
|
type = QueuedMedia.Type.VIDEO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMediaToQueue(media.getId(), type, media.getPreviewUrl(), media.getDescription());
|
||||||
|
}
|
||||||
} else if (savedMediaQueued != null) {
|
} else if (savedMediaQueued != null) {
|
||||||
for (SavedQueuedMedia item : savedMediaQueued) {
|
for (SavedQueuedMedia item : savedMediaQueued) {
|
||||||
Bitmap preview = getImageThumbnail(getContentResolver(), item.uri, thumbnailViewSize);
|
Bitmap preview = getImageThumbnail(getContentResolver(), item.uri, thumbnailViewSize);
|
||||||
|
@ -1111,6 +1140,11 @@ public final class ComposeActivity
|
||||||
addMediaToQueue(null, type, preview, uri, mediaSize, null, description);
|
addMediaToQueue(null, type, preview, uri, mediaSize, null, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addMediaToQueue(String id, QueuedMedia.Type type, String previewUrl, @Nullable String description) {
|
||||||
|
addMediaToQueue(id, type, null, Uri.parse(previewUrl), 0,
|
||||||
|
QueuedMedia.ReadyStage.UPLOADED, description);
|
||||||
|
}
|
||||||
|
|
||||||
private void addMediaToQueue(@Nullable String id, QueuedMedia.Type type, Bitmap preview, Uri uri,
|
private void addMediaToQueue(@Nullable String id, QueuedMedia.Type type, Bitmap preview, Uri uri,
|
||||||
long mediaSize, QueuedMedia.ReadyStage readyStage, @Nullable String description) {
|
long mediaSize, QueuedMedia.ReadyStage readyStage, @Nullable String description) {
|
||||||
final QueuedMedia item = new QueuedMedia(type, uri, new ProgressImageView(this),
|
final QueuedMedia item = new QueuedMedia(type, uri, new ProgressImageView(this),
|
||||||
|
@ -1126,7 +1160,14 @@ public final class ComposeActivity
|
||||||
layoutParams.setMargins(margin, 0, margin, marginBottom);
|
layoutParams.setMargins(margin, 0, margin, marginBottom);
|
||||||
view.setLayoutParams(layoutParams);
|
view.setLayoutParams(layoutParams);
|
||||||
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
|
if (preview != null) {
|
||||||
view.setImageBitmap(preview);
|
view.setImageBitmap(preview);
|
||||||
|
} else {
|
||||||
|
Glide.with(this)
|
||||||
|
.load(uri)
|
||||||
|
.placeholder(null)
|
||||||
|
.into(view);
|
||||||
|
}
|
||||||
view.setOnClickListener(v -> onMediaClick(item, v));
|
view.setOnClickListener(v -> onMediaClick(item, v));
|
||||||
view.setContentDescription(getString(R.string.action_delete));
|
view.setContentDescription(getString(R.string.action_delete));
|
||||||
mediaPreviewBar.addView(view);
|
mediaPreviewBar.addView(view);
|
||||||
|
@ -1782,7 +1823,7 @@ public final class ComposeActivity
|
||||||
@Nullable
|
@Nullable
|
||||||
private Integer savedTootUid;
|
private Integer savedTootUid;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String savedTootText;
|
private String tootText;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String savedJsonUrls;
|
private String savedJsonUrls;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -1794,21 +1835,25 @@ public final class ComposeActivity
|
||||||
@Nullable
|
@Nullable
|
||||||
private Status.Visibility replyVisibility;
|
private Status.Visibility replyVisibility;
|
||||||
@Nullable
|
@Nullable
|
||||||
private Status.Visibility savedVisibility;
|
private Status.Visibility visibility;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String contentWarning;
|
private String contentWarning;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String replyingStatusAuthor;
|
private String replyingStatusAuthor;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String replyingStatusContent;
|
private String replyingStatusContent;
|
||||||
|
@Nullable
|
||||||
|
private ArrayList<Attachment> mediaAttachments;
|
||||||
|
private boolean sensitive = false;
|
||||||
|
|
||||||
|
|
||||||
public IntentBuilder savedTootUid(int uid) {
|
public IntentBuilder savedTootUid(int uid) {
|
||||||
this.savedTootUid = uid;
|
this.savedTootUid = uid;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntentBuilder savedTootText(String savedTootText) {
|
public IntentBuilder tootText(String tootText) {
|
||||||
this.savedTootText = savedTootText;
|
this.tootText = tootText;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1822,8 +1867,8 @@ public final class ComposeActivity
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntentBuilder savedVisibility(Status.Visibility savedVisibility) {
|
public IntentBuilder visibility(Status.Visibility visibility) {
|
||||||
this.savedVisibility = savedVisibility;
|
this.visibility = visibility;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1857,14 +1902,24 @@ public final class ComposeActivity
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IntentBuilder mediaAttachments(ArrayList<Attachment> mediaAttachments) {
|
||||||
|
this.mediaAttachments = mediaAttachments;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntentBuilder sensitive(boolean sensitive) {
|
||||||
|
this.sensitive = sensitive;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Intent build(Context context) {
|
public Intent build(Context context) {
|
||||||
Intent intent = new Intent(context, ComposeActivity.class);
|
Intent intent = new Intent(context, ComposeActivity.class);
|
||||||
|
|
||||||
if (savedTootUid != null) {
|
if (savedTootUid != null) {
|
||||||
intent.putExtra(SAVED_TOOT_UID_EXTRA, (int) savedTootUid);
|
intent.putExtra(SAVED_TOOT_UID_EXTRA, (int) savedTootUid);
|
||||||
}
|
}
|
||||||
if (savedTootText != null) {
|
if (tootText != null) {
|
||||||
intent.putExtra(SAVED_TOOT_TEXT_EXTRA, savedTootText);
|
intent.putExtra(TOOT_TEXT_EXTRA, tootText);
|
||||||
}
|
}
|
||||||
if (savedJsonUrls != null) {
|
if (savedJsonUrls != null) {
|
||||||
intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls);
|
intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls);
|
||||||
|
@ -1882,8 +1937,8 @@ public final class ComposeActivity
|
||||||
if (replyVisibility != null) {
|
if (replyVisibility != null) {
|
||||||
intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility.getNum());
|
intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility.getNum());
|
||||||
}
|
}
|
||||||
if (savedVisibility != null) {
|
if (visibility != null) {
|
||||||
intent.putExtra(SAVED_TOOT_VISIBILITY_EXTRA, savedVisibility.getNum());
|
intent.putExtra(TOOT_VISIBILITY_EXTRA, visibility.getNum());
|
||||||
}
|
}
|
||||||
if (contentWarning != null) {
|
if (contentWarning != null) {
|
||||||
intent.putExtra(CONTENT_WARNING_EXTRA, contentWarning);
|
intent.putExtra(CONTENT_WARNING_EXTRA, contentWarning);
|
||||||
|
@ -1894,6 +1949,10 @@ public final class ComposeActivity
|
||||||
if (replyingStatusAuthor != null) {
|
if (replyingStatusAuthor != null) {
|
||||||
intent.putExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA, replyingStatusAuthor);
|
intent.putExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA, replyingStatusAuthor);
|
||||||
}
|
}
|
||||||
|
if (mediaAttachments != null) {
|
||||||
|
intent.putParcelableArrayListExtra(MEDIA_ATTACHMENTS_EXTRA, mediaAttachments);
|
||||||
|
}
|
||||||
|
intent.putExtra(SENSITIVE_EXTRA, sensitive);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,14 +155,14 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd
|
||||||
public void click(int position, TootEntity item) {
|
public void click(int position, TootEntity item) {
|
||||||
Intent intent = new ComposeActivity.IntentBuilder()
|
Intent intent = new ComposeActivity.IntentBuilder()
|
||||||
.savedTootUid(item.getUid())
|
.savedTootUid(item.getUid())
|
||||||
.savedTootText(item.getText())
|
.tootText(item.getText())
|
||||||
.contentWarning(item.getContentWarning())
|
.contentWarning(item.getContentWarning())
|
||||||
.savedJsonUrls(item.getUrls())
|
.savedJsonUrls(item.getUrls())
|
||||||
.savedJsonDescriptions(item.getDescriptions())
|
.savedJsonDescriptions(item.getDescriptions())
|
||||||
.inReplyToId(item.getInReplyToId())
|
.inReplyToId(item.getInReplyToId())
|
||||||
.replyingStatusAuthor(item.getInReplyToUsername())
|
.replyingStatusAuthor(item.getInReplyToUsername())
|
||||||
.replyingStatusContent(item.getInReplyToText())
|
.replyingStatusContent(item.getInReplyToText())
|
||||||
.savedVisibility(item.getVisibility())
|
.visibility(item.getVisibility())
|
||||||
.build(this);
|
.build(this);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ data class ConversationStatusEntity(
|
||||||
val favourited: Boolean,
|
val favourited: Boolean,
|
||||||
val sensitive: Boolean,
|
val sensitive: Boolean,
|
||||||
val spoilerText: String,
|
val spoilerText: String,
|
||||||
val attachments: List<Attachment>,
|
val attachments: ArrayList<Attachment>,
|
||||||
val mentions: Array<Status.Mention>,
|
val mentions: Array<Status.Mention>,
|
||||||
val showingHiddenContent: Boolean,
|
val showingHiddenContent: Boolean,
|
||||||
val expanded: Boolean,
|
val expanded: Boolean,
|
||||||
|
|
|
@ -95,8 +95,8 @@ class Converters {
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun jsonToAttachmentList(attachmentListJson: String?): List<Attachment>? {
|
fun jsonToAttachmentList(attachmentListJson: String?): ArrayList<Attachment>? {
|
||||||
return gson.fromJson(attachmentListJson, object : TypeToken<List<Attachment>>() {}.type)
|
return gson.fromJson(attachmentListJson, object : TypeToken<ArrayList<Attachment>>() {}.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
|
|
@ -36,7 +36,7 @@ data class Status(
|
||||||
var sensitive: Boolean,
|
var sensitive: Boolean,
|
||||||
@SerializedName("spoiler_text") val spoilerText: String,
|
@SerializedName("spoiler_text") val spoilerText: String,
|
||||||
val visibility: Visibility,
|
val visibility: Visibility,
|
||||||
@SerializedName("media_attachments") var attachments: List<Attachment>,
|
@SerializedName("media_attachments") var attachments: ArrayList<Attachment>,
|
||||||
val mentions: Array<Mention>,
|
val mentions: Array<Mention>,
|
||||||
val application: Application?,
|
val application: Application?,
|
||||||
var pinned: Boolean?
|
var pinned: Boolean?
|
||||||
|
|
|
@ -21,17 +21,25 @@ import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.BaseActivity;
|
import com.keylesspalace.tusky.BaseActivity;
|
||||||
import com.keylesspalace.tusky.BottomSheetActivity;
|
import com.keylesspalace.tusky.BottomSheetActivity;
|
||||||
import com.keylesspalace.tusky.ComposeActivity;
|
import com.keylesspalace.tusky.ComposeActivity;
|
||||||
|
@ -56,13 +64,6 @@ import java.util.Set;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
|
|
||||||
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an
|
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an
|
||||||
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature
|
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature
|
||||||
* of that is complicated by how they're coupled with Status and Notification and the corresponding
|
* of that is complicated by how they're coupled with Status and Notification and the corresponding
|
||||||
|
@ -274,6 +275,10 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
showConfirmDeleteDialog(id, position);
|
showConfirmDeleteDialog(id, position);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.status_delete_and_redraft: {
|
||||||
|
showConfirmEditDialog(id, position, status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.pin: {
|
case R.id.pin: {
|
||||||
timelineCases.pin(status, !status.isPinned());
|
timelineCases.pin(status, !status.isPinned());
|
||||||
return true;
|
return true;
|
||||||
|
@ -343,6 +348,46 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showConfirmEditDialog(final String id, final int position, Status status) {
|
||||||
|
if (getActivity() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage(R.string.dialog_redraft_toot_warning)
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
|
||||||
|
timelineCases.delete(id);
|
||||||
|
removeItem(position);
|
||||||
|
|
||||||
|
Intent intent = new ComposeActivity.IntentBuilder()
|
||||||
|
.tootText(getEditableText(status.getContent(), status.getMentions()))
|
||||||
|
.inReplyToId(status.getInReplyToId())
|
||||||
|
.visibility(status.getVisibility())
|
||||||
|
.contentWarning(status.getSpoilerText())
|
||||||
|
.mediaAttachments(status.getAttachments())
|
||||||
|
.sensitive(status.getSensitive())
|
||||||
|
.build(getContext());
|
||||||
|
startActivity(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getEditableText(Spanned content, Status.Mention[] mentions) {
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||||
|
for (URLSpan span : content.getSpans(0, content.length(), URLSpan.class)) {
|
||||||
|
String url = span.getURL();
|
||||||
|
for (Status.Mention mention : mentions) {
|
||||||
|
if (url.equals(mention.getUrl())) {
|
||||||
|
int start = builder.getSpanStart(span);
|
||||||
|
int end = builder.getSpanEnd(span);
|
||||||
|
builder.replace(start, end, '@' + mention.getUsername());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private void openAsAccount(String statusUrl, AccountEntity account) {
|
private void openAsAccount(String statusUrl, AccountEntity account) {
|
||||||
accountManager.setActiveAccount(account);
|
accountManager.setActiveAccount(account);
|
||||||
Intent intent = new Intent(getContext(), MainActivity.class);
|
Intent intent = new Intent(getContext(), MainActivity.class);
|
||||||
|
|
|
@ -11,12 +11,16 @@ import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
|
import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
|
||||||
import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK
|
import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.Either
|
||||||
|
import com.keylesspalace.tusky.util.HtmlConverter
|
||||||
|
import com.keylesspalace.tusky.util.dec
|
||||||
|
import com.keylesspalace.tusky.util.inc
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
data class Placeholder(val id: String)
|
data class Placeholder(val id: String)
|
||||||
|
|
||||||
|
@ -191,8 +195,8 @@ class TimelineRepositoryImpl(
|
||||||
return Either.Left(Placeholder(this.status.serverId))
|
return Either.Left(Placeholder(this.status.serverId))
|
||||||
}
|
}
|
||||||
|
|
||||||
val attachments: List<Attachment> = gson.fromJson(status.attachments,
|
val attachments: ArrayList<Attachment> = gson.fromJson(status.attachments,
|
||||||
object : TypeToken<List<Attachment>>() {}.type) ?: listOf()
|
object : TypeToken<List<Attachment>>() {}.type) ?: ArrayList()
|
||||||
val mentions: Array<Status.Mention> = gson.fromJson(status.mentions,
|
val mentions: Array<Status.Mention> = gson.fromJson(status.mentions,
|
||||||
Array<Status.Mention>::class.java) ?: arrayOf()
|
Array<Status.Mention>::class.java) ?: arrayOf()
|
||||||
val application = gson.fromJson(status.application, Status.Application::class.java)
|
val application = gson.fromJson(status.application, Status.Application::class.java)
|
||||||
|
@ -242,7 +246,7 @@ class TimelineRepositoryImpl(
|
||||||
sensitive = false,
|
sensitive = false,
|
||||||
spoilerText = "",
|
spoilerText = "",
|
||||||
visibility = status.visibility!!,
|
visibility = status.visibility!!,
|
||||||
attachments = listOf(),
|
attachments = ArrayList(),
|
||||||
mentions = arrayOf(),
|
mentions = arrayOf(),
|
||||||
application = null,
|
application = null,
|
||||||
pinned = false
|
pinned = false
|
||||||
|
|
|
@ -29,4 +29,7 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/status_delete"
|
android:id="@+id/status_delete"
|
||||||
android:title="@string/action_delete" />
|
android:title="@string/action_delete" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/status_delete_and_redraft"
|
||||||
|
android:title="@string/action_delete_and_redraft" />
|
||||||
</menu>
|
</menu>
|
|
@ -74,6 +74,7 @@
|
||||||
<string name="action_show_reblogs">ブーストを表示</string>
|
<string name="action_show_reblogs">ブーストを表示</string>
|
||||||
<string name="action_report">通報</string>
|
<string name="action_report">通報</string>
|
||||||
<string name="action_delete">削除</string>
|
<string name="action_delete">削除</string>
|
||||||
|
<string name="action_delete_and_redraft">削除して編集</string>
|
||||||
<string name="action_send">トゥート</string>
|
<string name="action_send">トゥート</string>
|
||||||
<string name="action_send_public">トゥート!</string>
|
<string name="action_send_public">トゥート!</string>
|
||||||
<string name="action_retry">再試行</string>
|
<string name="action_retry">再試行</string>
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
<string name="action_show_reblogs">Show boosts</string>
|
<string name="action_show_reblogs">Show boosts</string>
|
||||||
<string name="action_report">Report</string>
|
<string name="action_report">Report</string>
|
||||||
<string name="action_delete">Delete</string>
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_delete_and_redraft">Delete and re-draft</string>
|
||||||
<string name="action_send">TOOT</string>
|
<string name="action_send">TOOT</string>
|
||||||
<string name="action_send_public">TOOT!</string>
|
<string name="action_send_public">TOOT!</string>
|
||||||
<string name="action_retry">Retry</string>
|
<string name="action_retry">Retry</string>
|
||||||
|
@ -179,6 +180,7 @@
|
||||||
<string name="dialog_message_cancel_follow_request">Revoke the follow request?</string>
|
<string name="dialog_message_cancel_follow_request">Revoke the follow request?</string>
|
||||||
<string name="dialog_unfollow_warning">Unfollow this account?</string>
|
<string name="dialog_unfollow_warning">Unfollow this account?</string>
|
||||||
<string name="dialog_delete_toot_warning">Delete this toot?</string>
|
<string name="dialog_delete_toot_warning">Delete this toot?</string>
|
||||||
|
<string name="dialog_redraft_toot_warning">Delete and re-draft this toot?</string>
|
||||||
|
|
||||||
<string name="visibility_public">Public: Post to public timelines</string>
|
<string name="visibility_public">Public: Post to public timelines</string>
|
||||||
<string name="visibility_unlisted">Unlisted: Do not show in public timelines</string>
|
<string name="visibility_unlisted">Unlisted: Do not show in public timelines</string>
|
||||||
|
|
|
@ -81,7 +81,7 @@ class BottomSheetActivityTest {
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
Status.Visibility.PUBLIC,
|
Status.Visibility.PUBLIC,
|
||||||
listOf(),
|
ArrayList(),
|
||||||
arrayOf(),
|
arrayOf(),
|
||||||
null,
|
null,
|
||||||
pinned = false
|
pinned = false
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.mockito.Mock
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class TimelineRepositoryTest {
|
class TimelineRepositoryTest {
|
||||||
@Mock
|
@Mock
|
||||||
|
@ -297,7 +298,7 @@ class TimelineRepositoryTest {
|
||||||
spoilerText = "",
|
spoilerText = "",
|
||||||
reblogged = true,
|
reblogged = true,
|
||||||
favourited = false,
|
favourited = false,
|
||||||
attachments = listOf(),
|
attachments = ArrayList(),
|
||||||
mentions = arrayOf(),
|
mentions = arrayOf(),
|
||||||
application = null,
|
application = null,
|
||||||
inReplyToAccountId = null,
|
inReplyToAccountId = null,
|
||||||
|
|
Loading…
Reference in a new issue