Media description improvements (#898)

* Enforce 420-character limit on media descriptions in the UI

* Persist media descriptions with drafts

* Defer media description update until after upload finishes

* Make description field 2 lines for better visibility of hint text

* Reuse Gson instance

* Force retranslation of modified string "hint_describe_for_visually_impaired"

* Add bounds check when reading serialized media descriptions
This commit is contained in:
Levi Bard 2018-11-11 19:25:45 +01:00 committed by Konrad Pozniak
parent 026292122d
commit 690e612f8b
30 changed files with 103 additions and 48 deletions

View file

@ -61,6 +61,7 @@ import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.Editable; import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -172,6 +173,7 @@ public final class ComposeActivity
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 SAVED_TOOT_TEXT_EXTRA = "saved_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_TOOT_VISIBILITY_EXTRA = "saved_toot_visibility"; private static final String SAVED_TOOT_VISIBILITY_EXTRA = "saved_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_visibilty";
@ -181,6 +183,8 @@ public final class ComposeActivity
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content"; private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content";
// 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
private static final int MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420;
@Inject @Inject
public MastodonApi mastodonApi; public MastodonApi mastodonApi;
@ -226,6 +230,7 @@ public final class ComposeActivity
private @Px int thumbnailViewSize; private @Px int thumbnailViewSize;
private SaveTootHelper saveTootHelper; private SaveTootHelper saveTootHelper;
private Gson gson = new Gson();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -396,6 +401,7 @@ public final class ComposeActivity
String[] mentionedUsernames = null; String[] mentionedUsernames = null;
ArrayList<String> loadedDraftMediaUris = null; ArrayList<String> loadedDraftMediaUris = null;
ArrayList<String> loadedDraftMediaDescriptions = null;
inReplyToId = null; inReplyToId = null;
if (intent != null) { if (intent != null) {
@ -428,10 +434,16 @@ public final class ComposeActivity
textEditor.setText(savedTootText); textEditor.setText(savedTootText);
} }
// try to redo a list of media
String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA); String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA);
String savedJsonDescriptions = intent.getStringExtra(SAVED_JSON_DESCRIPTIONS_EXTRA);
if (!TextUtils.isEmpty(savedJsonUrls)) { if (!TextUtils.isEmpty(savedJsonUrls)) {
// try to redo a list of media loadedDraftMediaUris = gson.fromJson(savedJsonUrls,
loadedDraftMediaUris = new Gson().fromJson(savedJsonUrls, new TypeToken<ArrayList<String>>() {
}.getType());
}
if (!TextUtils.isEmpty(savedJsonDescriptions)) {
loadedDraftMediaDescriptions = gson.fromJson(savedJsonDescriptions,
new TypeToken<ArrayList<String>>() { new TypeToken<ArrayList<String>>() {
}.getType()); }.getType());
} }
@ -550,10 +562,14 @@ public final class ComposeActivity
// These can only be added after everything affected by the media queue is initialized. // These can only be added after everything affected by the media queue is initialized.
if (!ListUtils.isEmpty(loadedDraftMediaUris)) { if (!ListUtils.isEmpty(loadedDraftMediaUris)) {
for (String uriString : loadedDraftMediaUris) { for (int mediaIndex = 0; mediaIndex < loadedDraftMediaUris.size(); ++mediaIndex) {
Uri uri = Uri.parse(uriString); Uri uri = Uri.parse(loadedDraftMediaUris.get(mediaIndex));
long mediaSize = getMediaSize(getContentResolver(), uri); long mediaSize = getMediaSize(getContentResolver(), uri);
pickMedia(uri, mediaSize); String description = null;
if (loadedDraftMediaDescriptions != null && mediaIndex < loadedDraftMediaDescriptions.size()) {
description = loadedDraftMediaDescriptions.get(mediaIndex);
}
pickMedia(uri, mediaSize, description);
} }
} else if (savedMediaQueued != null) { } else if (savedMediaQueued != null) {
for (SavedQueuedMedia item : savedMediaQueued) { for (SavedQueuedMedia item : savedMediaQueued) {
@ -593,7 +609,7 @@ public final class ComposeActivity
} }
for (Uri uri : uriList) { for (Uri uri : uriList) {
long mediaSize = getMediaSize(getContentResolver(), uri); long mediaSize = getMediaSize(getContentResolver(), uri);
pickMedia(uri, mediaSize); pickMedia(uri, mediaSize, null);
} }
} else if (type.equals("text/plain")) { } else if (type.equals("text/plain")) {
String action = intent.getAction(); String action = intent.getAction();
@ -610,6 +626,9 @@ public final class ComposeActivity
} }
} }
} }
for (QueuedMedia item : mediaQueued) {
item.preview.setChecked(!TextUtils.isEmpty(item.description));
}
textEditor.requestFocus(); textEditor.requestFocus();
} }
@ -880,7 +899,7 @@ public final class ComposeActivity
} else { } else {
mediaSize = MEDIA_SIZE_UNKNOWN; mediaSize = MEDIA_SIZE_UNKNOWN;
} }
pickMedia(uri, mediaSize); pickMedia(uri, mediaSize, null);
currentInputContentInfo = inputContentInfo; currentInputContentInfo = inputContentInfo;
currentFlags = flags; currentFlags = flags;
@ -892,13 +911,15 @@ public final class ComposeActivity
String spoilerText) { String spoilerText) {
ArrayList<String> mediaIds = new ArrayList<>(); ArrayList<String> mediaIds = new ArrayList<>();
ArrayList<Uri> mediaUris = new ArrayList<>(); ArrayList<Uri> mediaUris = new ArrayList<>();
ArrayList<String> mediaDescriptions = new ArrayList<>();
for (QueuedMedia item : mediaQueued) { for (QueuedMedia item : mediaQueued) {
mediaIds.add(item.id); mediaIds.add(item.id);
mediaUris.add(item.uri); mediaUris.add(item.uri);
mediaDescriptions.add(item.description);
} }
Intent sendIntent = SendTootService.sendTootIntent(this, content, spoilerText, Intent sendIntent = SendTootService.sendTootIntent(this, content, spoilerText,
visibility, sensitive, mediaIds, mediaUris, inReplyToId, visibility, sensitive, mediaIds, mediaUris, mediaDescriptions, inReplyToId,
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA), getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA), getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA),
getIntent().getStringExtra(SAVED_JSON_URLS_EXTRA), getIntent().getStringExtra(SAVED_JSON_URLS_EXTRA),
@ -1051,8 +1072,8 @@ public final class ComposeActivity
colorActive ? android.R.attr.textColorTertiary : R.attr.compose_media_button_disabled_tint); colorActive ? android.R.attr.textColorTertiary : R.attr.compose_media_button_disabled_tint);
} }
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize) { private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize, @Nullable String description) {
addMediaToQueue(null, type, preview, uri, mediaSize, null, null); addMediaToQueue(null, type, preview, uri, mediaSize, null, 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,
@ -1161,16 +1182,17 @@ public final class ComposeActivity
((LinearLayout.LayoutParams) imageView.getLayoutParams()).setMargins(0, margin, 0, 0); ((LinearLayout.LayoutParams) imageView.getLayoutParams()).setMargins(0, margin, 0, 0);
EditText input = new EditText(this); EditText input = new EditText(this);
input.setHint(R.string.hint_describe_for_visually_impaired); input.setHint(getString(R.string.hint_describe_for_visually_impaired, MEDIA_DESCRIPTION_CHARACTER_LIMIT));
dialogLayout.addView(input); dialogLayout.addView(input);
((LinearLayout.LayoutParams) input.getLayoutParams()).setMargins(margin, margin, margin, margin); ((LinearLayout.LayoutParams) input.getLayoutParams()).setMargins(margin, margin, margin, margin);
input.setLines(1); input.setLines(2);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
input.setText(item.description); input.setText(item.description);
input.setFilters(new InputFilter[] { new InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT) });
DialogInterface.OnClickListener okListener = (dialog, which) -> { DialogInterface.OnClickListener okListener = (dialog, which) -> {
mastodonApi.updateMedia(item.id, input.getText().toString()) Runnable updateDescription = () -> {
.enqueue(new Callback<Attachment>() { mastodonApi.updateMedia(item.id, input.getText().toString()).enqueue(new Callback<Attachment>() {
@Override @Override
public void onResponse(@NonNull Call<Attachment> call, @NonNull Response<Attachment> response) { public void onResponse(@NonNull Call<Attachment> call, @NonNull Response<Attachment> response) {
Attachment attachment = response.body(); Attachment attachment = response.body();
@ -1181,13 +1203,23 @@ public final class ComposeActivity
} else { } else {
showFailedCaptionMessage(); showFailedCaptionMessage();
} }
item.updateDescription = null;
} }
@Override @Override
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
showFailedCaptionMessage(); showFailedCaptionMessage();
item.updateDescription = null;
} }
}); });
};
if (item.readyStage == QueuedMedia.ReadyStage.UPLOADED) {
updateDescription.run();
} else {
// media is still uploading, queue description update for when it finishes
item.updateDescription = updateDescription;
}
}; };
AlertDialog dialog = new AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this)
@ -1301,6 +1333,9 @@ public final class ComposeActivity
public void onResponse(@NonNull Call<Attachment> call, @NonNull retrofit2.Response<Attachment> response) { public void onResponse(@NonNull Call<Attachment> call, @NonNull retrofit2.Response<Attachment> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
onUploadSuccess(item, response.body()); onUploadSuccess(item, response.body());
if (item.updateDescription != null) {
item.updateDescription.run();
}
} else { } else {
Log.d(TAG, "Upload request failed. " + response.message()); Log.d(TAG, "Upload request failed. " + response.message());
onUploadFailure(item, call.isCanceled()); onUploadFailure(item, call.isCanceled());
@ -1311,6 +1346,7 @@ public final class ComposeActivity
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
Log.d(TAG, "Upload request failed. " + t.getMessage()); Log.d(TAG, "Upload request failed. " + t.getMessage());
onUploadFailure(item, call.isCanceled()); onUploadFailure(item, call.isCanceled());
item.updateDescription = null;
} }
}); });
} }
@ -1355,15 +1391,15 @@ public final class ComposeActivity
if (resultCode == RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) { if (resultCode == RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) {
Uri uri = intent.getData(); Uri uri = intent.getData();
long mediaSize = getMediaSize(getContentResolver(), uri); long mediaSize = getMediaSize(getContentResolver(), uri);
pickMedia(uri, mediaSize); pickMedia(uri, mediaSize, null);
} else if (resultCode == RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) { } else if (resultCode == RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) {
long mediaSize = getMediaSize(getContentResolver(), photoUploadUri); long mediaSize = getMediaSize(getContentResolver(), photoUploadUri);
pickMedia(photoUploadUri, mediaSize); pickMedia(photoUploadUri, mediaSize, null);
} }
} }
private void pickMedia(Uri uri, long mediaSize) { private void pickMedia(Uri uri, long mediaSize, String description) {
if (mediaSize == MEDIA_SIZE_UNKNOWN) { if (mediaSize == MEDIA_SIZE_UNKNOWN) {
displayTransientError(R.string.error_media_upload_opening); displayTransientError(R.string.error_media_upload_opening);
return; return;
@ -1385,7 +1421,7 @@ public final class ComposeActivity
} }
Bitmap bitmap = getVideoThumbnail(this, uri, thumbnailViewSize); Bitmap bitmap = getVideoThumbnail(this, uri, thumbnailViewSize);
if (bitmap != null) { if (bitmap != null) {
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize); addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize, description);
} else { } else {
displayTransientError(R.string.error_media_upload_opening); displayTransientError(R.string.error_media_upload_opening);
} }
@ -1394,7 +1430,7 @@ public final class ComposeActivity
case "image": { case "image": {
Bitmap bitmap = getImageThumbnail(contentResolver, uri, thumbnailViewSize); Bitmap bitmap = getImageThumbnail(contentResolver, uri, thumbnailViewSize);
if (bitmap != null) { if (bitmap != null) {
addMediaToQueue(QueuedMedia.Type.IMAGE, bitmap, uri, mediaSize); addMediaToQueue(QueuedMedia.Type.IMAGE, bitmap, uri, mediaSize, description);
} else { } else {
displayTransientError(R.string.error_media_upload_opening); displayTransientError(R.string.error_media_upload_opening);
} }
@ -1466,14 +1502,17 @@ public final class ComposeActivity
private void saveDraftAndFinish() { private void saveDraftAndFinish() {
ArrayList<String> mediaUris = new ArrayList<>(); ArrayList<String> mediaUris = new ArrayList<>();
ArrayList<String> mediaDescriptions = new ArrayList<>();
for (QueuedMedia item : mediaQueued) { for (QueuedMedia item : mediaQueued) {
mediaUris.add(item.uri.toString()); mediaUris.add(item.uri.toString());
mediaDescriptions.add(item.description);
} }
saveTootHelper.saveToot(textEditor.getText().toString(), saveTootHelper.saveToot(textEditor.getText().toString(),
contentWarningEditor.getText().toString(), contentWarningEditor.getText().toString(),
getIntent().getStringExtra("saved_json_urls"), getIntent().getStringExtra("saved_json_urls"),
mediaUris, mediaUris,
mediaDescriptions,
savedTootUid, savedTootUid,
inReplyToId, inReplyToId,
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA), getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
@ -1543,6 +1582,7 @@ public final class ComposeActivity
ReadyStage readyStage; ReadyStage readyStage;
long mediaSize; long mediaSize;
String description; String description;
Runnable updateDescription;
QueuedMedia(Type type, Uri uri, ProgressImageView preview, long mediaSize, QueuedMedia(Type type, Uri uri, ProgressImageView preview, long mediaSize,
String description) { String description) {
@ -1628,6 +1668,8 @@ public final class ComposeActivity
@Nullable @Nullable
private String savedJsonUrls; private String savedJsonUrls;
@Nullable @Nullable
private String savedJsonDescriptions;
@Nullable
private Collection<String> mentionedUsernames; private Collection<String> mentionedUsernames;
@Nullable @Nullable
private String inReplyToId; private String inReplyToId;
@ -1657,6 +1699,11 @@ public final class ComposeActivity
return this; return this;
} }
public IntentBuilder savedJsonDescriptions(String jsonDescriptions) {
this.savedJsonDescriptions = jsonDescriptions;
return this;
}
public IntentBuilder savedVisibility(Status.Visibility savedVisibility) { public IntentBuilder savedVisibility(Status.Visibility savedVisibility) {
this.savedVisibility = savedVisibility; this.savedVisibility = savedVisibility;
return this; return this;
@ -1704,6 +1751,9 @@ public final class ComposeActivity
if (savedJsonUrls != null) { if (savedJsonUrls != null) {
intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls); intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls);
} }
if (savedJsonDescriptions != null) {
intent.putExtra(SAVED_JSON_DESCRIPTIONS_EXTRA, savedJsonDescriptions);
}
if (mentionedUsernames != null) { if (mentionedUsernames != null) {
String[] usernames = mentionedUsernames.toArray(new String[0]); String[] usernames = mentionedUsernames.toArray(new String[0]);
intent.putExtra(MENTIONED_USERNAMES_EXTRA, usernames); intent.putExtra(MENTIONED_USERNAMES_EXTRA, usernames);

View file

@ -163,6 +163,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd
.savedTootText(item.getText()) .savedTootText(item.getText())
.contentWarning(item.getContentWarning()) .contentWarning(item.getContentWarning())
.savedJsonUrls(item.getUrls()) .savedJsonUrls(item.getUrls())
.savedJsonDescriptions(item.getDescriptions())
.inReplyToId(item.getInReplyToId()) .inReplyToId(item.getInReplyToId())
.repyingStatusAuthor(item.getInReplyToUsername()) .repyingStatusAuthor(item.getInReplyToUsername())
.replyingStatusContent(item.getInReplyToText()) .replyingStatusContent(item.getInReplyToText())

View file

@ -70,7 +70,7 @@ public class TuskyApplication extends Application implements HasActivityInjector
appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB") appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB")
.allowMainThreadQueries() .allowMainThreadQueries()
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5,
AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8) AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, AppDatabase.MIGRATION_8_9)
.build(); .build();
accountManager = new AccountManager(appDatabase); accountManager = new AccountManager(appDatabase);
serviceLocator = new ServiceLocator() { serviceLocator = new ServiceLocator() {

View file

@ -25,7 +25,7 @@ import android.support.annotation.NonNull;
* DB version & declare DAO * DB version & declare DAO
*/ */
@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class}, version = 8, exportSchema = false) @Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class}, version = 9, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
public abstract TootDao tootDao(); public abstract TootDao tootDao();
@ -98,4 +98,11 @@ public abstract class AppDatabase extends RoomDatabase {
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `emojis` TEXT NOT NULL DEFAULT '[]'"); database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `emojis` TEXT NOT NULL DEFAULT '[]'");
} }
}; };
public static final Migration MIGRATION_8_9 = new Migration(8, 9) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `descriptions` TEXT DEFAULT '[]'");
}
};
} }

View file

@ -40,6 +40,9 @@ public class TootEntity {
@ColumnInfo(name = "urls") @ColumnInfo(name = "urls")
private final String urls; private final String urls;
@ColumnInfo(name = "descriptions")
private final String descriptions;
@ColumnInfo(name = "contentWarning") @ColumnInfo(name = "contentWarning")
private final String contentWarning; private final String contentWarning;
@ -57,12 +60,13 @@ public class TootEntity {
@ColumnInfo(name = "visibility") @ColumnInfo(name = "visibility")
private final Status.Visibility visibility; private final Status.Visibility visibility;
public TootEntity(int uid, String text, String urls, String contentWarning, String inReplyToId, public TootEntity(int uid, String text, String urls, String descriptions, String contentWarning, String inReplyToId,
@Nullable String inReplyToText, @Nullable String inReplyToUsername, @Nullable String inReplyToText, @Nullable String inReplyToUsername,
Status.Visibility visibility) { Status.Visibility visibility) {
this.uid = uid; this.uid = uid;
this.text = text; this.text = text;
this.urls = urls; this.urls = urls;
this.descriptions = descriptions;
this.contentWarning = contentWarning; this.contentWarning = contentWarning;
this.inReplyToId = inReplyToId; this.inReplyToId = inReplyToId;
this.inReplyToText = inReplyToText; this.inReplyToText = inReplyToText;
@ -86,6 +90,10 @@ public class TootEntity {
return urls; return urls;
} }
public String getDescriptions() {
return descriptions;
}
public String getInReplyToId() { public String getInReplyToId() {
return inReplyToId; return inReplyToId;
} }

View file

@ -92,6 +92,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
false, false,
emptyList(), emptyList(),
emptyList(), emptyList(),
emptyList(),
citedStatusId, citedStatusId,
null, null,
null, null,

View file

@ -238,6 +238,7 @@ class SendTootService : Service(), Injectable {
toot.warningText, toot.warningText,
toot.savedJsonUrls, toot.savedJsonUrls,
toot.mediaUris, toot.mediaUris,
toot.mediaDescriptions,
toot.savedTootUid, toot.savedTootUid,
toot.inReplyToId, toot.inReplyToId,
toot.replyingStatusContent, toot.replyingStatusContent,
@ -274,6 +275,7 @@ class SendTootService : Service(), Injectable {
sensitive: Boolean, sensitive: Boolean,
mediaIds: List<String>, mediaIds: List<String>,
mediaUris: List<Uri>, mediaUris: List<Uri>,
mediaDescriptions: List<String>,
inReplyToId: String?, inReplyToId: String?,
replyingStatusContent: String?, replyingStatusContent: String?,
replyingStatusAuthorUsername: String?, replyingStatusAuthorUsername: String?,
@ -291,6 +293,7 @@ class SendTootService : Service(), Injectable {
sensitive, sensitive,
mediaIds, mediaIds,
mediaUris.map { it.toString() }, mediaUris.map { it.toString() },
mediaDescriptions,
inReplyToId, inReplyToId,
replyingStatusContent, replyingStatusContent,
replyingStatusAuthorUsername, replyingStatusAuthorUsername,
@ -332,6 +335,7 @@ data class TootToSend(val text: String,
val sensitive: Boolean, val sensitive: Boolean,
val mediaIds: List<String>, val mediaIds: List<String>,
val mediaUris: List<String>, val mediaUris: List<String>,
val mediaDescriptions: List<String>,
val inReplyToId: String?, val inReplyToId: String?,
val replyingStatusContent: String?, val replyingStatusContent: String?,
val replyingStatusAuthorUsername: String?, val replyingStatusAuthorUsername: String?,

View file

@ -32,6 +32,7 @@ public final class SaveTootHelper {
private TootDao tootDao; private TootDao tootDao;
private Context context; private Context context;
private Gson gson = new Gson();
public SaveTootHelper(@NonNull TootDao tootDao, @NonNull Context context) { public SaveTootHelper(@NonNull TootDao tootDao, @NonNull Context context) {
this.tootDao = tootDao; this.tootDao = tootDao;
@ -43,6 +44,7 @@ public final class SaveTootHelper {
@NonNull String contentWarning, @NonNull String contentWarning,
@Nullable String savedJsonUrls, @Nullable String savedJsonUrls,
@NonNull List<String> mediaUris, @NonNull List<String> mediaUris,
@NonNull List<String> mediaDescriptions,
int savedTootUid, int savedTootUid,
@Nullable String inReplyToId, @Nullable String inReplyToId,
@Nullable String replyingStatusContent, @Nullable String replyingStatusContent,
@ -56,28 +58,31 @@ public final class SaveTootHelper {
// Get any existing file's URIs. // Get any existing file's URIs.
ArrayList<String> existingUris = null; ArrayList<String> existingUris = null;
if (!TextUtils.isEmpty(savedJsonUrls)) { if (!TextUtils.isEmpty(savedJsonUrls)) {
existingUris = new Gson().fromJson(savedJsonUrls, existingUris = gson.fromJson(savedJsonUrls,
new TypeToken<ArrayList<String>>() { new TypeToken<ArrayList<String>>() {
}.getType()); }.getType());
} }
String mediaUrlsSerialized = null; String mediaUrlsSerialized = null;
String mediaDescriptionsSerialized = null;
if (!ListUtils.isEmpty(mediaUris)) { if (!ListUtils.isEmpty(mediaUris)) {
List<String> savedList = saveMedia(mediaUris, existingUris); List<String> savedList = saveMedia(mediaUris, existingUris);
if (!ListUtils.isEmpty(savedList)) { if (!ListUtils.isEmpty(savedList)) {
mediaUrlsSerialized = new Gson().toJson(savedList); mediaUrlsSerialized = gson.toJson(savedList);
if (!ListUtils.isEmpty(existingUris)) { if (!ListUtils.isEmpty(existingUris)) {
deleteMedia(setDifference(existingUris, savedList)); deleteMedia(setDifference(existingUris, savedList));
} }
} else { } else {
return false; return false;
} }
mediaDescriptionsSerialized = gson.toJson(mediaDescriptions);
} else if (!ListUtils.isEmpty(existingUris)) { } else if (!ListUtils.isEmpty(existingUris)) {
/* If there were URIs in the previous draft, but they've now been removed, those files /* If there were URIs in the previous draft, but they've now been removed, those files
* can be deleted. */ * can be deleted. */
deleteMedia(existingUris); deleteMedia(existingUris);
} }
final TootEntity toot = new TootEntity(savedTootUid, content, mediaUrlsSerialized, contentWarning, final TootEntity toot = new TootEntity(savedTootUid, content, mediaUrlsSerialized, mediaDescriptionsSerialized, contentWarning,
inReplyToId, inReplyToId,
replyingStatusContent, replyingStatusContent,
replyingStatusAuthorUsername, replyingStatusAuthorUsername,
@ -102,7 +107,7 @@ public final class SaveTootHelper {
public void deleteDraft(@NonNull TootEntity item){ public void deleteDraft(@NonNull TootEntity item){
// Delete any media files associated with the status. // Delete any media files associated with the status.
ArrayList<String> uris = new Gson().fromJson(item.getUrls(), ArrayList<String> uris = gson.fromJson(item.getUrls(),
new TypeToken<ArrayList<String>>() {}.getType()); new TypeToken<ArrayList<String>>() {}.getType());
if (uris != null) { if (uris != null) {
for (String uriString : uris) { for (String uriString : uris) {

View file

@ -265,7 +265,6 @@
<string name="compose_active_account_description">النشر بواسطة حساب %1$s</string> <string name="compose_active_account_description">النشر بواسطة حساب %1$s</string>
<string name="error_failed_set_caption">تعذرت عملية إضافة الشرح</string> <string name="error_failed_set_caption">تعذرت عملية إضافة الشرح</string>
<string name="hint_describe_for_visually_impaired">وصف للمعاقين بصريًا</string>
<string name="action_set_caption">إضافة شرح</string> <string name="action_set_caption">إضافة شرح</string>
<string name="action_remove_media">حذف</string> <string name="action_remove_media">حذف</string>
<string name="lock_account_label">تجميد الحساب</string> <string name="lock_account_label">تجميد الحساب</string>

View file

@ -305,7 +305,6 @@
<string name="compose_active_account_description">Yn postio â chyfrif %1$s</string> <string name="compose_active_account_description">Yn postio â chyfrif %1$s</string>
<string name="error_failed_set_caption">Methu gosod pennawd</string> <string name="error_failed_set_caption">Methu gosod pennawd</string>
<string name="hint_describe_for_visually_impaired">Disgrifiad i bobl â nam ar y golwg</string>
<string name="action_set_caption">Pennu pennawd</string> <string name="action_set_caption">Pennu pennawd</string>
<string name="action_remove_media">Dileu</string> <string name="action_remove_media">Dileu</string>
<string name="lock_account_label">Cloi cyfrif</string> <string name="lock_account_label">Cloi cyfrif</string>

View file

@ -249,7 +249,6 @@
<string name="title_lists">Listen</string> <string name="title_lists">Listen</string>
<string name="title_list_timeline">Liste</string> <string name="title_list_timeline">Liste</string>
<string name="action_remove_media">entfernen</string> <string name="action_remove_media">entfernen</string>
<string name="hint_describe_for_visually_impaired">Beschreibung für Menschen mit Sehbehinderung</string>
<string name="action_set_caption">Beschreibung eingeben</string> <string name="action_set_caption">Beschreibung eingeben</string>
<string name="error_failed_set_caption">Fehler beim Speichern der Beschreibung</string> <string name="error_failed_set_caption">Fehler beim Speichern der Beschreibung</string>
<string name="add_account_name">Konto hinzufügen</string> <string name="add_account_name">Konto hinzufügen</string>

View file

@ -300,7 +300,6 @@
<string name="compose_active_account_description">Publicando con la cuenta %1$s</string> <string name="compose_active_account_description">Publicando con la cuenta %1$s</string>
<string name="error_failed_set_caption">Error al añadir leyenda</string> <string name="error_failed_set_caption">Error al añadir leyenda</string>
<string name="hint_describe_for_visually_impaired">Descripción para discapacitados visuales</string>
<string name="action_set_caption">Añadir leyenda</string> <string name="action_set_caption">Añadir leyenda</string>
<string name="action_remove_media">Eliminar</string> <string name="action_remove_media">Eliminar</string>
<string name="lock_account_label">Proteger cuenta</string> <string name="lock_account_label">Proteger cuenta</string>

View file

@ -305,7 +305,6 @@
<string name="compose_active_account_description" >پست با حساب %1$s</string> <string name="compose_active_account_description" >پست با حساب %1$s</string>
<string name="error_failed_set_caption" >ناتوان در تنظیم عنوان</string> <string name="error_failed_set_caption" >ناتوان در تنظیم عنوان</string>
<string name="hint_describe_for_visually_impaired" >توصیف برای ضعف دیداری</string>
<string name="action_set_caption" >تنظیم عنوان</string> <string name="action_set_caption" >تنظیم عنوان</string>
<string name="action_remove_media" >حذف</string> <string name="action_remove_media" >حذف</string>
<string name="lock_account_label" >قفل حساب</string> <string name="lock_account_label" >قفل حساب</string>

View file

@ -268,7 +268,6 @@
<string name="compose_active_account_description">Poster avec le compte %1$s</string> <string name="compose_active_account_description">Poster avec le compte %1$s</string>
<string name="error_failed_set_caption">Impossible de mettre la légende</string> <string name="error_failed_set_caption">Impossible de mettre la légende</string>
<string name="hint_describe_for_visually_impaired">Décrire pour les mal voyants</string>
<string name="action_set_caption">Mettre une légende</string> <string name="action_set_caption">Mettre une légende</string>
<string name="action_remove_media">Supprimer le média</string> <string name="action_remove_media">Supprimer le média</string>
<string name="lock_account_label">Verrouiller le compte</string> <string name="lock_account_label">Verrouiller le compte</string>

View file

@ -305,7 +305,6 @@
<string name="compose_active_account_description">Pubblicando con l\'account %1$s</string> <string name="compose_active_account_description">Pubblicando con l\'account %1$s</string>
<string name="error_failed_set_caption">Impostazione del sottotitolo non riuscita</string> <string name="error_failed_set_caption">Impostazione del sottotitolo non riuscita</string>
<string name="hint_describe_for_visually_impaired">Descrivi per persone ipovedenti</string>
<string name="action_set_caption">Inserisci sottotitolo</string> <string name="action_set_caption">Inserisci sottotitolo</string>
<string name="action_remove_media">Rimuovi</string> <string name="action_remove_media">Rimuovi</string>
<string name="lock_account_label">Blocca account</string> <string name="lock_account_label">Blocca account</string>

View file

@ -291,7 +291,6 @@
<string name="title_list_timeline">リストタイムライン</string> <string name="title_list_timeline">リストタイムライン</string>
<string name="error_failed_set_caption">説明の設定に失敗しました</string> <string name="error_failed_set_caption">説明の設定に失敗しました</string>
<string name="hint_describe_for_visually_impaired">視覚障害者のための説明</string>
<string name="action_set_caption">説明を設定</string> <string name="action_set_caption">説明を設定</string>
<string name="action_remove_media">消去</string> <string name="action_remove_media">消去</string>
<string name="lock_account_label">アカウントをロック</string> <string name="lock_account_label">アカウントをロック</string>

View file

@ -264,7 +264,6 @@
<string name="compose_active_account_description">다음 계정으로 작성 중: %1$s</string> <string name="compose_active_account_description">다음 계정으로 작성 중: %1$s</string>
<string name="error_failed_set_caption">캡션 설정 실패</string> <string name="error_failed_set_caption">캡션 설정 실패</string>
<string name="hint_describe_for_visually_impaired">시각 장애인을 위한 설명</string>
<string name="action_set_caption">설명 쓰기</string> <string name="action_set_caption">설명 쓰기</string>
<string name="action_remove_media">삭제</string> <string name="action_remove_media">삭제</string>
<string name="lock_account_label">계정 잠금</string> <string name="lock_account_label">계정 잠금</string>

View file

@ -292,7 +292,6 @@
<string name="compose_active_account_description">Aan het publiceren met account %1$s</string> <string name="compose_active_account_description">Aan het publiceren met account %1$s</string>
<string name="error_failed_set_caption">Toevoegen van beschrijving mislukt</string> <string name="error_failed_set_caption">Toevoegen van beschrijving mislukt</string>
<string name="hint_describe_for_visually_impaired">Omschrijf dit voor mensen met een visuele beperking</string>
<string name="action_set_caption">Beschrijving toevoegen</string> <string name="action_set_caption">Beschrijving toevoegen</string>
<string name="action_remove_media">Verwijderen</string> <string name="action_remove_media">Verwijderen</string>
<string name="lock_account_label">Account besloten maken</string> <string name="lock_account_label">Account besloten maken</string>

View file

@ -299,7 +299,6 @@
<string name="compose_active_account_description">Publicar amb lo compte %1$s</string> <string name="compose_active_account_description">Publicar amb lo compte %1$s</string>
<string name="error_failed_set_caption">Fracàs en apondre una legenda</string> <string name="error_failed_set_caption">Fracàs en apondre una legenda</string>
<string name="hint_describe_for_visually_impaired">Descripcion pels mal vesents</string>
<string name="action_set_caption">Apondre una legenda</string> <string name="action_set_caption">Apondre una legenda</string>
<string name="action_remove_media">Levar</string> <string name="action_remove_media">Levar</string>
<string name="lock_account_label">Clavar lo compte</string> <string name="lock_account_label">Clavar lo compte</string>

View file

@ -300,7 +300,6 @@
<string name="compose_active_account_description">Publikowanie z konta %1$s</string> <string name="compose_active_account_description">Publikowanie z konta %1$s</string>
<string name="error_failed_set_caption">Nie udało się ustawić podpisu</string> <string name="error_failed_set_caption">Nie udało się ustawić podpisu</string>
<string name="hint_describe_for_visually_impaired">Opis dla osób korzystających z ułatwień dostępności</string>
<string name="action_set_caption">Ustaw podpis</string> <string name="action_set_caption">Ustaw podpis</string>
<string name="action_remove_media">Usuń</string> <string name="action_remove_media">Usuń</string>
<string name="lock_account_label">Zablokuj konto</string> <string name="lock_account_label">Zablokuj konto</string>

View file

@ -296,7 +296,6 @@
<string name="compose_active_account_description">Postando com a conta %1$s</string> <string name="compose_active_account_description">Postando com a conta %1$s</string>
<string name="error_failed_set_caption">Falha ao atribuir descrição</string> <string name="error_failed_set_caption">Falha ao atribuir descrição</string>
<string name="hint_describe_for_visually_impaired">Descreva a imagem para deficientes visuais</string>
<string name="action_set_caption">Escrever descrição</string> <string name="action_set_caption">Escrever descrição</string>
<string name="action_remove_media">Remover</string> <string name="action_remove_media">Remover</string>
<string name="lock_account_label">Trancar conta</string> <string name="lock_account_label">Trancar conta</string>

View file

@ -285,7 +285,6 @@
<string name="compose_active_account_description">Отправка от имени %1$s</string> <string name="compose_active_account_description">Отправка от имени %1$s</string>
<string name="error_failed_set_caption">Не удалось добавить подпись</string> <string name="error_failed_set_caption">Не удалось добавить подпись</string>
<string name="hint_describe_for_visually_impaired">Описание для слабовидящих</string>
<string name="action_set_caption">Добавить подпись</string> <string name="action_set_caption">Добавить подпись</string>
<string name="action_remove_media">Удалить</string> <string name="action_remove_media">Удалить</string>
<string name="lock_account_label">Закрыть аккаунт</string> <string name="lock_account_label">Закрыть аккаунт</string>

View file

@ -304,7 +304,6 @@
<string name="compose_active_account_description">Inlägg med kontot %1$s</string> <string name="compose_active_account_description">Inlägg med kontot %1$s</string>
<string name="error_failed_set_caption">Misslyckades med att ange bildtext</string> <string name="error_failed_set_caption">Misslyckades med att ange bildtext</string>
<string name="hint_describe_for_visually_impaired">Beskriv för synskadade</string>
<string name="action_set_caption">Ange bildtext</string> <string name="action_set_caption">Ange bildtext</string>
<string name="action_remove_media">Ta bort</string> <string name="action_remove_media">Ta bort</string>
<string name="lock_account_label">Lås konto</string> <string name="lock_account_label">Lås konto</string>

View file

@ -288,7 +288,6 @@
<string name="compose_active_account_description">%1$s கணக்குடன் பதிவிட</string> <string name="compose_active_account_description">%1$s கணக்குடன் பதிவிட</string>
<string name="error_failed_set_caption">தலைப்பை அமைக்க முடியவில்லை</string> <string name="error_failed_set_caption">தலைப்பை அமைக்க முடியவில்லை</string>
<string name="hint_describe_for_visually_impaired">பார்வையற்றவர்களுக்கான விளக்கம்</string>
<string name="action_set_caption">தலைப்பை அமை</string> <string name="action_set_caption">தலைப்பை அமை</string>
<string name="action_remove_media">நீக்கு</string> <string name="action_remove_media">நீக்கு</string>
<string name="lock_account_label">கணக்கை முடக்கு</string> <string name="lock_account_label">கணக்கை முடக்கு</string>

View file

@ -275,7 +275,6 @@
<string name="compose_active_account_description">使用帐号 %1$s 发布嘟文</string> <string name="compose_active_account_description">使用帐号 %1$s 发布嘟文</string>
<string name="error_failed_set_caption">设置图片标题失败。</string> <string name="error_failed_set_caption">设置图片标题失败。</string>
<string name="hint_describe_for_visually_impaired">为视觉障碍者提供的描述</string>
<string name="action_set_caption">设置图片标题</string> <string name="action_set_caption">设置图片标题</string>
<string name="action_remove_media">移除</string> <string name="action_remove_media">移除</string>
<string name="lock_account_label">保护你的帐户(锁嘟)</string> <string name="lock_account_label">保护你的帐户(锁嘟)</string>

View file

@ -275,7 +275,6 @@
<string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string> <string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string>
<string name="error_failed_set_caption">設置圖片標題失敗。</string> <string name="error_failed_set_caption">設置圖片標題失敗。</string>
<string name="hint_describe_for_visually_impaired">為視覺障礙者提供的描述</string>
<string name="action_set_caption">設置圖片標題</string> <string name="action_set_caption">設置圖片標題</string>
<string name="action_remove_media">移除</string> <string name="action_remove_media">移除</string>
<string name="lock_account_label">保護你的帳户(鎖嘟)</string> <string name="lock_account_label">保護你的帳户(鎖嘟)</string>

View file

@ -275,7 +275,6 @@
<string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string> <string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string>
<string name="error_failed_set_caption">設置圖片標題失敗。</string> <string name="error_failed_set_caption">設置圖片標題失敗。</string>
<string name="hint_describe_for_visually_impaired">為視覺障礙者提供的描述</string>
<string name="action_set_caption">設置圖片標題</string> <string name="action_set_caption">設置圖片標題</string>
<string name="action_remove_media">移除</string> <string name="action_remove_media">移除</string>
<string name="lock_account_label">保護你的帳户(鎖嘟)</string> <string name="lock_account_label">保護你的帳户(鎖嘟)</string>

View file

@ -275,7 +275,6 @@
<string name="compose_active_account_description">使用帐号 %1$s 发布嘟文</string> <string name="compose_active_account_description">使用帐号 %1$s 发布嘟文</string>
<string name="error_failed_set_caption">设置图片标题失败。</string> <string name="error_failed_set_caption">设置图片标题失败。</string>
<string name="hint_describe_for_visually_impaired">为视觉障碍者提供的描述</string>
<string name="action_set_caption">设置图片标题</string> <string name="action_set_caption">设置图片标题</string>
<string name="action_remove_media">移除</string> <string name="action_remove_media">移除</string>
<string name="lock_account_label">保护你的帐户(锁嘟)</string> <string name="lock_account_label">保护你的帐户(锁嘟)</string>

View file

@ -275,7 +275,6 @@
<string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string> <string name="compose_active_account_description">使用帳號 %1$s 發佈嘟文</string>
<string name="error_failed_set_caption">設置圖片標題失敗。</string> <string name="error_failed_set_caption">設置圖片標題失敗。</string>
<string name="hint_describe_for_visually_impaired">為視覺障礙者提供的描述</string>
<string name="action_set_caption">設置圖片標題</string> <string name="action_set_caption">設置圖片標題</string>
<string name="action_remove_media">移除</string> <string name="action_remove_media">移除</string>
<string name="lock_account_label">保護你的帳戶(鎖嘟)</string> <string name="lock_account_label">保護你的帳戶(鎖嘟)</string>

View file

@ -305,7 +305,7 @@
<string name="compose_active_account_description">Posting with account %1$s</string> <string name="compose_active_account_description">Posting with account %1$s</string>
<string name="error_failed_set_caption">Failed to set caption</string> <string name="error_failed_set_caption">Failed to set caption</string>
<string name="hint_describe_for_visually_impaired">Describe for visually impaired</string> <string name="hint_describe_for_visually_impaired">Describe for visually impaired\n(%d character limit)</string>
<string name="action_set_caption">Set caption</string> <string name="action_set_caption">Set caption</string>
<string name="action_remove_media">Remove</string> <string name="action_remove_media">Remove</string>
<string name="lock_account_label">Lock account</string> <string name="lock_account_label">Lock account</string>