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.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.google.android.material.bottomsheet.BottomSheetBehavior; | ||||
| import com.google.android.material.snackbar.Snackbar; | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.keylesspalace.tusky.adapter.EmojiAdapter; | ||||
| import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter; | ||||
| import com.keylesspalace.tusky.adapter.EmojiAdapter; | ||||
| import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener; | ||||
| import com.keylesspalace.tusky.db.AccountEntity; | ||||
| 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.ProgressRequestBody; | ||||
| import com.keylesspalace.tusky.service.SendTootService; | ||||
| import com.keylesspalace.tusky.util.ComposeTokenizer; | ||||
| import com.keylesspalace.tusky.util.CountUpDownLatch; | ||||
| import com.keylesspalace.tusky.util.DownsizeImageTask; | ||||
| import com.keylesspalace.tusky.util.ListUtils; | ||||
| import com.keylesspalace.tusky.util.ComposeTokenizer; | ||||
| import com.keylesspalace.tusky.util.SaveTootHelper; | ||||
| import com.keylesspalace.tusky.util.SpanUtilsKt; | ||||
| import com.keylesspalace.tusky.util.StringUtils; | ||||
|  | @ -114,24 +133,6 @@ import java.util.concurrent.CountDownLatch; | |||
| 
 | ||||
| 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 io.reactivex.Single; | ||||
| 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 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_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 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 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_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 | ||||
|     static final int MAXIMUM_URL_LENGTH = 23; | ||||
|     // https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94 | ||||
|  | @ -409,6 +412,7 @@ public final class ComposeActivity | |||
|         String[] mentionedUsernames = null; | ||||
|         ArrayList<String> loadedDraftMediaUris = null; | ||||
|         ArrayList<String> loadedDraftMediaDescriptions = null; | ||||
|         ArrayList<Attachment> mediaAttachments = null; | ||||
|         inReplyToId = null; | ||||
|         if (intent != null) { | ||||
| 
 | ||||
|  | @ -432,14 +436,13 @@ public final class ComposeActivity | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // If come from SavedTootActivity | ||||
|             String savedTootText = intent.getStringExtra(SAVED_TOOT_TEXT_EXTRA); | ||||
|             if (!TextUtils.isEmpty(savedTootText)) { | ||||
|                 startingText = savedTootText; | ||||
|                 textEditor.setText(savedTootText); | ||||
|             String tootText = intent.getStringExtra(TOOT_TEXT_EXTRA); | ||||
|             if (!TextUtils.isEmpty(tootText)) { | ||||
|                 textEditor.setText(tootText); | ||||
|             } | ||||
| 
 | ||||
|             // try to redo a list of media | ||||
|             // If come from SavedTootActivity | ||||
|             String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA); | ||||
|             String savedJsonDescriptions = intent.getStringExtra(SAVED_JSON_DESCRIPTIONS_EXTRA); | ||||
|             if (!TextUtils.isEmpty(savedJsonUrls)) { | ||||
|  | @ -452,15 +455,20 @@ public final class ComposeActivity | |||
|                         new TypeToken<ArrayList<String>>() { | ||||
|                         }.getType()); | ||||
|             } | ||||
|             // If come from redraft | ||||
|             mediaAttachments = intent.getParcelableArrayListExtra(MEDIA_ATTACHMENTS_EXTRA); | ||||
| 
 | ||||
|             int savedTootUid = intent.getIntExtra(SAVED_TOOT_UID_EXTRA, 0); | ||||
|             if (savedTootUid != 0) { | ||||
|                 this.savedTootUid = savedTootUid; | ||||
| 
 | ||||
|                 // If come from SavedTootActivity | ||||
|                 startingText = tootText; | ||||
|             } | ||||
| 
 | ||||
|             int savedTootVisibility = intent.getIntExtra(SAVED_TOOT_VISIBILITY_EXTRA, Status.Visibility.UNKNOWN.getNum()); | ||||
|             if (savedTootVisibility != Status.Visibility.UNKNOWN.getNum()) { | ||||
|                 startingVisibility = Status.Visibility.byNum(savedTootVisibility); | ||||
|             int tootVisibility = intent.getIntExtra(TOOT_VISIBILITY_EXTRA, Status.Visibility.UNKNOWN.getNum()); | ||||
|             if (tootVisibility != Status.Visibility.UNKNOWN.getNum()) { | ||||
|                 startingVisibility = Status.Visibility.byNum(tootVisibility); | ||||
|             } | ||||
| 
 | ||||
|             if (intent.hasExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA)) { | ||||
|  | @ -491,6 +499,8 @@ public final class ComposeActivity | |||
|             if (intent.hasExtra(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. | ||||
|  | @ -575,6 +585,25 @@ public final class ComposeActivity | |||
|                 } | ||||
|                 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) { | ||||
|             for (SavedQueuedMedia item : savedMediaQueued) { | ||||
|                 Bitmap preview = getImageThumbnail(getContentResolver(), item.uri, thumbnailViewSize); | ||||
|  | @ -1111,6 +1140,11 @@ public final class ComposeActivity | |||
|         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, | ||||
|                                  long mediaSize, QueuedMedia.ReadyStage readyStage, @Nullable String description) { | ||||
|         final QueuedMedia item = new QueuedMedia(type, uri, new ProgressImageView(this), | ||||
|  | @ -1126,7 +1160,14 @@ public final class ComposeActivity | |||
|         layoutParams.setMargins(margin, 0, margin, marginBottom); | ||||
|         view.setLayoutParams(layoutParams); | ||||
|         view.setScaleType(ImageView.ScaleType.CENTER_CROP); | ||||
|         view.setImageBitmap(preview); | ||||
|         if (preview != null) { | ||||
|             view.setImageBitmap(preview); | ||||
|         } else { | ||||
|             Glide.with(this) | ||||
|                     .load(uri) | ||||
|                     .placeholder(null) | ||||
|                     .into(view); | ||||
|         } | ||||
|         view.setOnClickListener(v -> onMediaClick(item, v)); | ||||
|         view.setContentDescription(getString(R.string.action_delete)); | ||||
|         mediaPreviewBar.addView(view); | ||||
|  | @ -1782,7 +1823,7 @@ public final class ComposeActivity | |||
|         @Nullable | ||||
|         private Integer savedTootUid; | ||||
|         @Nullable | ||||
|         private String savedTootText; | ||||
|         private String tootText; | ||||
|         @Nullable | ||||
|         private String savedJsonUrls; | ||||
|         @Nullable | ||||
|  | @ -1794,21 +1835,25 @@ public final class ComposeActivity | |||
|         @Nullable | ||||
|         private Status.Visibility replyVisibility; | ||||
|         @Nullable | ||||
|         private Status.Visibility savedVisibility; | ||||
|         private Status.Visibility visibility; | ||||
|         @Nullable | ||||
|         private String contentWarning; | ||||
|         @Nullable | ||||
|         private String replyingStatusAuthor; | ||||
|         @Nullable | ||||
|         private String replyingStatusContent; | ||||
|         @Nullable | ||||
|         private ArrayList<Attachment> mediaAttachments; | ||||
|         private boolean sensitive = false; | ||||
| 
 | ||||
| 
 | ||||
|         public IntentBuilder savedTootUid(int uid) { | ||||
|             this.savedTootUid = uid; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public IntentBuilder savedTootText(String savedTootText) { | ||||
|             this.savedTootText = savedTootText; | ||||
|         public IntentBuilder tootText(String tootText) { | ||||
|             this.tootText = tootText; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|  | @ -1822,8 +1867,8 @@ public final class ComposeActivity | |||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public IntentBuilder savedVisibility(Status.Visibility savedVisibility) { | ||||
|             this.savedVisibility = savedVisibility; | ||||
|         public IntentBuilder visibility(Status.Visibility visibility) { | ||||
|             this.visibility = visibility; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|  | @ -1857,14 +1902,24 @@ public final class ComposeActivity | |||
|             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) { | ||||
|             Intent intent = new Intent(context, ComposeActivity.class); | ||||
| 
 | ||||
|             if (savedTootUid != null) { | ||||
|                 intent.putExtra(SAVED_TOOT_UID_EXTRA, (int) savedTootUid); | ||||
|             } | ||||
|             if (savedTootText != null) { | ||||
|                 intent.putExtra(SAVED_TOOT_TEXT_EXTRA, savedTootText); | ||||
|             if (tootText != null) { | ||||
|                 intent.putExtra(TOOT_TEXT_EXTRA, tootText); | ||||
|             } | ||||
|             if (savedJsonUrls != null) { | ||||
|                 intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls); | ||||
|  | @ -1882,8 +1937,8 @@ public final class ComposeActivity | |||
|             if (replyVisibility != null) { | ||||
|                 intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility.getNum()); | ||||
|             } | ||||
|             if (savedVisibility != null) { | ||||
|                 intent.putExtra(SAVED_TOOT_VISIBILITY_EXTRA, savedVisibility.getNum()); | ||||
|             if (visibility != null) { | ||||
|                 intent.putExtra(TOOT_VISIBILITY_EXTRA, visibility.getNum()); | ||||
|             } | ||||
|             if (contentWarning != null) { | ||||
|                 intent.putExtra(CONTENT_WARNING_EXTRA, contentWarning); | ||||
|  | @ -1894,6 +1949,10 @@ public final class ComposeActivity | |||
|             if (replyingStatusAuthor != null) { | ||||
|                 intent.putExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA, replyingStatusAuthor); | ||||
|             } | ||||
|             if (mediaAttachments != null) { | ||||
|                 intent.putParcelableArrayListExtra(MEDIA_ATTACHMENTS_EXTRA, mediaAttachments); | ||||
|             } | ||||
|             intent.putExtra(SENSITIVE_EXTRA, sensitive); | ||||
|             return intent; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -155,14 +155,14 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd | |||
|     public void click(int position, TootEntity item) { | ||||
|         Intent intent = new ComposeActivity.IntentBuilder() | ||||
|                 .savedTootUid(item.getUid()) | ||||
|                 .savedTootText(item.getText()) | ||||
|                 .tootText(item.getText()) | ||||
|                 .contentWarning(item.getContentWarning()) | ||||
|                 .savedJsonUrls(item.getUrls()) | ||||
|                 .savedJsonDescriptions(item.getDescriptions()) | ||||
|                 .inReplyToId(item.getInReplyToId()) | ||||
|                 .replyingStatusAuthor(item.getInReplyToUsername()) | ||||
|                 .replyingStatusContent(item.getInReplyToText()) | ||||
|                 .savedVisibility(item.getVisibility()) | ||||
|                 .visibility(item.getVisibility()) | ||||
|                 .build(this); | ||||
|         startActivity(intent); | ||||
|     } | ||||
|  |  | |||
|  | @ -71,7 +71,7 @@ data class ConversationStatusEntity( | |||
|         val favourited: Boolean, | ||||
|         val sensitive: Boolean, | ||||
|         val spoilerText: String, | ||||
|         val attachments: List<Attachment>, | ||||
|         val attachments: ArrayList<Attachment>, | ||||
|         val mentions: Array<Status.Mention>, | ||||
|         val showingHiddenContent: Boolean, | ||||
|         val expanded: Boolean, | ||||
|  |  | |||
|  | @ -95,8 +95,8 @@ class Converters { | |||
|     } | ||||
| 
 | ||||
|     @TypeConverter | ||||
|     fun jsonToAttachmentList(attachmentListJson: String?): List<Attachment>? { | ||||
|         return gson.fromJson(attachmentListJson, object : TypeToken<List<Attachment>>() {}.type) | ||||
|     fun jsonToAttachmentList(attachmentListJson: String?): ArrayList<Attachment>? { | ||||
|         return gson.fromJson(attachmentListJson, object : TypeToken<ArrayList<Attachment>>() {}.type) | ||||
|     } | ||||
| 
 | ||||
|     @TypeConverter | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ data class Status( | |||
|         var sensitive: Boolean, | ||||
|         @SerializedName("spoiler_text") val spoilerText: String, | ||||
|         val visibility: Visibility, | ||||
|         @SerializedName("media_attachments") var attachments: List<Attachment>, | ||||
|         @SerializedName("media_attachments") var attachments: ArrayList<Attachment>, | ||||
|         val mentions: Array<Mention>, | ||||
|         val application: Application?, | ||||
|         var pinned: Boolean? | ||||
|  |  | |||
|  | @ -21,17 +21,25 @@ import android.content.ClipData; | |||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| 
 | ||||
| import android.content.pm.PackageManager; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Environment; | ||||
| import android.text.SpannableStringBuilder; | ||||
| import android.text.Spanned; | ||||
| import android.text.style.URLSpan; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| 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.BottomSheetActivity; | ||||
| import com.keylesspalace.tusky.ComposeActivity; | ||||
|  | @ -56,13 +64,6 @@ import java.util.Set; | |||
| 
 | ||||
| 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 | ||||
|  * 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 | ||||
|  | @ -274,6 +275,10 @@ public abstract class SFragment extends BaseFragment implements Injectable { | |||
|                     showConfirmDeleteDialog(id, position); | ||||
|                     return true; | ||||
|                 } | ||||
|                 case R.id.status_delete_and_redraft: { | ||||
|                     showConfirmEditDialog(id, position, status); | ||||
|                     return true; | ||||
|                 } | ||||
|                 case R.id.pin: { | ||||
|                     timelineCases.pin(status, !status.isPinned()); | ||||
|                     return true; | ||||
|  | @ -343,6 +348,46 @@ public abstract class SFragment extends BaseFragment implements Injectable { | |||
|                 .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) { | ||||
|         accountManager.setActiveAccount(account); | ||||
|         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.repository.TimelineRequestMode.DISK | ||||
| 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.schedulers.Schedulers | ||||
| import java.io.IOException | ||||
| import java.util.* | ||||
| import java.util.concurrent.TimeUnit | ||||
| import kotlin.collections.ArrayList | ||||
| 
 | ||||
| data class Placeholder(val id: String) | ||||
| 
 | ||||
|  | @ -191,8 +195,8 @@ class TimelineRepositoryImpl( | |||
|             return Either.Left(Placeholder(this.status.serverId)) | ||||
|         } | ||||
| 
 | ||||
|         val attachments: List<Attachment> = gson.fromJson(status.attachments, | ||||
|                 object : TypeToken<List<Attachment>>() {}.type) ?: listOf() | ||||
|         val attachments: ArrayList<Attachment> = gson.fromJson(status.attachments, | ||||
|                 object : TypeToken<List<Attachment>>() {}.type) ?: ArrayList() | ||||
|         val mentions: Array<Status.Mention> = gson.fromJson(status.mentions, | ||||
|                 Array<Status.Mention>::class.java) ?: arrayOf() | ||||
|         val application = gson.fromJson(status.application, Status.Application::class.java) | ||||
|  | @ -242,7 +246,7 @@ class TimelineRepositoryImpl( | |||
|                     sensitive = false, | ||||
|                     spoilerText = "", | ||||
|                     visibility = status.visibility!!, | ||||
|                     attachments = listOf(), | ||||
|                     attachments = ArrayList(), | ||||
|                     mentions = arrayOf(), | ||||
|                     application = null, | ||||
|                     pinned = false | ||||
|  |  | |||
|  | @ -29,4 +29,7 @@ | |||
|     <item | ||||
|         android:id="@+id/status_delete" | ||||
|         android:title="@string/action_delete" /> | ||||
|     <item | ||||
|         android:id="@+id/status_delete_and_redraft" | ||||
|         android:title="@string/action_delete_and_redraft" /> | ||||
| </menu> | ||||
|  | @ -74,6 +74,7 @@ | |||
|     <string name="action_show_reblogs">ブーストを表示</string> | ||||
|     <string name="action_report">通報</string> | ||||
|     <string name="action_delete">削除</string> | ||||
|     <string name="action_delete_and_redraft">削除して編集</string> | ||||
|     <string name="action_send">トゥート</string> | ||||
|     <string name="action_send_public">トゥート!</string> | ||||
|     <string name="action_retry">再試行</string> | ||||
|  |  | |||
|  | @ -83,6 +83,7 @@ | |||
|     <string name="action_show_reblogs">Show boosts</string> | ||||
|     <string name="action_report">Report</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_public">TOOT!</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_unfollow_warning">Unfollow this account?</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_unlisted">Unlisted: Do not show in public timelines</string> | ||||
|  |  | |||
|  | @ -81,7 +81,7 @@ class BottomSheetActivityTest { | |||
|             false, | ||||
|             "", | ||||
|             Status.Visibility.PUBLIC, | ||||
|             listOf(), | ||||
|             ArrayList(), | ||||
|             arrayOf(), | ||||
|             null, | ||||
|             pinned = false | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ import org.mockito.Mock | |||
| import org.mockito.MockitoAnnotations | ||||
| import java.util.* | ||||
| import java.util.concurrent.TimeUnit | ||||
| import kotlin.collections.ArrayList | ||||
| 
 | ||||
| class TimelineRepositoryTest { | ||||
|     @Mock | ||||
|  | @ -297,7 +298,7 @@ class TimelineRepositoryTest { | |||
|                 spoilerText = "", | ||||
|                 reblogged = true, | ||||
|                 favourited = false, | ||||
|                 attachments = listOf(), | ||||
|                 attachments = ArrayList(), | ||||
|                 mentions = arrayOf(), | ||||
|                 application = null, | ||||
|                 inReplyToAccountId = null, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue