2017-01-20 19:09:10 +11:00
|
|
|
|
/* Copyright 2017 Andrew Dawson
|
|
|
|
|
*
|
2017-04-10 10:12:31 +10:00
|
|
|
|
* This file is a part of Tusky.
|
2017-01-20 19:09:10 +11:00
|
|
|
|
*
|
2017-04-10 10:12:31 +10:00
|
|
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
|
|
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
|
|
|
* License, or (at your option) any later version.
|
2017-01-20 19:09:10 +11:00
|
|
|
|
*
|
|
|
|
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
2017-04-10 10:12:31 +10:00
|
|
|
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
|
|
|
* Public License for more details.
|
2017-01-20 19:09:10 +11:00
|
|
|
|
*
|
2017-04-10 10:12:31 +10:00
|
|
|
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
2017-01-20 19:09:10 +11:00
|
|
|
|
|
2017-01-08 09:24:02 +11:00
|
|
|
|
package com.keylesspalace.tusky;
|
|
|
|
|
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.Manifest;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
import android.annotation.SuppressLint;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.app.ProgressDialog;
|
|
|
|
|
import android.content.ContentResolver;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
import android.content.Context;
|
2017-01-19 05:35:07 +11:00
|
|
|
|
import android.content.DialogInterface;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.content.Intent;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.content.SharedPreferences;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.content.pm.PackageManager;
|
2017-03-04 12:44:44 +11:00
|
|
|
|
import android.content.res.AssetFileDescriptor;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.content.res.Resources;
|
|
|
|
|
import android.graphics.Bitmap;
|
2017-03-27 09:26:47 +11:00
|
|
|
|
import android.graphics.drawable.Drawable;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.AsyncTask;
|
|
|
|
|
import android.os.Build;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.os.Bundle;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
import android.os.Environment;
|
2017-02-04 11:53:33 +11:00
|
|
|
|
import android.os.Parcel;
|
2017-02-18 09:56:31 +11:00
|
|
|
|
import android.os.Parcelable;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
import android.provider.MediaStore;
|
2017-05-03 08:17:54 +10:00
|
|
|
|
import android.support.annotation.AttrRes;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.support.annotation.NonNull;
|
2017-06-19 13:34:48 +10:00
|
|
|
|
import android.support.annotation.Nullable;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
import android.support.annotation.Px;
|
2017-02-23 06:13:51 +11:00
|
|
|
|
import android.support.annotation.StringRes;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.support.design.widget.Snackbar;
|
2017-03-04 12:44:44 +11:00
|
|
|
|
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
|
|
|
|
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.support.v4.app.ActivityCompat;
|
|
|
|
|
import android.support.v4.content.ContextCompat;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
import android.support.v4.content.FileProvider;
|
2017-03-08 00:09:33 +11:00
|
|
|
|
import android.support.v7.app.ActionBar;
|
2017-07-28 12:40:58 +10:00
|
|
|
|
import android.support.v7.app.AlertDialog;
|
2017-04-03 09:10:07 +10:00
|
|
|
|
import android.support.v7.content.res.AppCompatResources;
|
2017-03-08 00:09:33 +11:00
|
|
|
|
import android.support.v7.widget.Toolbar;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.text.Editable;
|
2017-10-24 07:55:43 +11:00
|
|
|
|
import android.text.InputType;
|
2017-05-04 10:27:59 +10:00
|
|
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
|
import android.text.Spanned;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
import android.text.TextUtils;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.text.TextWatcher;
|
2017-05-04 10:27:59 +10:00
|
|
|
|
import android.text.style.URLSpan;
|
2017-05-24 05:34:31 +10:00
|
|
|
|
import android.util.Log;
|
2017-03-08 00:09:33 +11:00
|
|
|
|
import android.view.MenuItem;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.view.View;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.webkit.MimeTypeMap;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
import android.widget.Button;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.widget.EditText;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
import android.widget.ImageButton;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import android.widget.ImageView;
|
|
|
|
|
import android.widget.LinearLayout;
|
2017-04-11 00:23:44 +10:00
|
|
|
|
import android.widget.ProgressBar;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
import android.widget.TextView;
|
2017-06-30 01:56:58 +10:00
|
|
|
|
import android.widget.Toast;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
2017-07-06 00:36:14 +10:00
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
import com.google.gson.reflect.TypeToken;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
import com.keylesspalace.tusky.adapter.MentionAutoCompleteAdapter;
|
2017-06-30 01:56:58 +10:00
|
|
|
|
import com.keylesspalace.tusky.db.TootDao;
|
|
|
|
|
import com.keylesspalace.tusky.db.TootEntity;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
import com.keylesspalace.tusky.entity.Account;
|
2017-03-10 02:59:18 +11:00
|
|
|
|
import com.keylesspalace.tusky.entity.Media;
|
2017-03-09 10:27:37 +11:00
|
|
|
|
import com.keylesspalace.tusky.entity.Status;
|
2017-05-05 08:55:34 +10:00
|
|
|
|
import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
|
2017-10-30 08:18:45 +11:00
|
|
|
|
import com.keylesspalace.tusky.network.ProgressRequestBody;
|
2017-05-05 08:55:34 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.CountUpDownLatch;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.DownsizeImageTask;
|
2017-05-05 08:55:34 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.IOUtils;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.ListUtils;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.MediaUtils;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.MentionTokenizer;
|
2017-05-05 08:55:34 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.SpanUtils;
|
2017-07-03 06:34:10 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.StringUtils;
|
2017-05-05 08:55:34 +10:00
|
|
|
|
import com.keylesspalace.tusky.util.ThemeUtils;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
import com.keylesspalace.tusky.view.EditTextTyped;
|
2017-10-30 08:18:45 +11:00
|
|
|
|
import com.keylesspalace.tusky.view.ProgressImageView;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
2017-04-16 01:43:26 +10:00
|
|
|
|
import java.io.File;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import java.io.FileNotFoundException;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
import java.io.FileOutputStream;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
import java.text.SimpleDateFormat;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import java.util.ArrayList;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
import java.util.Collection;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import java.util.Date;
|
2017-01-19 05:35:07 +11:00
|
|
|
|
import java.util.Iterator;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
import java.util.List;
|
2017-03-06 11:58:36 +11:00
|
|
|
|
import java.util.Locale;
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
2017-03-10 02:59:18 +11:00
|
|
|
|
import okhttp3.MediaType;
|
|
|
|
|
import okhttp3.MultipartBody;
|
|
|
|
|
import retrofit2.Call;
|
|
|
|
|
import retrofit2.Callback;
|
2017-05-04 10:27:59 +10:00
|
|
|
|
import retrofit2.Response;
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
public final class ComposeActivity extends BaseActivity
|
|
|
|
|
implements ComposeOptionsFragment.Listener, MentionAutoCompleteAdapter.AccountSearchProvider {
|
2017-03-10 10:20:08 +11:00
|
|
|
|
private static final String TAG = "ComposeActivity"; // logging tag
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private static final int STATUS_CHARACTER_LIMIT = 500;
|
2017-07-26 14:12:02 +10:00
|
|
|
|
private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608; // 8MiB
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private static final int MEDIA_PICK_RESULT = 1;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
private static final int MEDIA_TAKE_PHOTO_RESULT = 2;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
2017-04-16 03:44:29 +10:00
|
|
|
|
private static final int COMPOSE_SUCCESS = -1;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@Px
|
|
|
|
|
private static final int THUMBNAIL_SIZE = 128;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
|
|
|
|
|
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_JSON_URLS_EXTRA = "saved_json_urls";
|
|
|
|
|
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 CONTENT_WARNING_EXTRA = "content_warning";
|
|
|
|
|
private static final String MENTIONED_USERNAMES_EXTRA = "netnioned_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";
|
2017-11-17 05:18:11 +11:00
|
|
|
|
|
|
|
|
|
private static final String REMEMBERED_VISIBILITY_PREF = "rememberedVisibilityNum";
|
2017-06-30 01:56:58 +10:00
|
|
|
|
private static TootDao tootDao = TuskyApplication.getDB().tootDao();
|
2017-06-23 04:59:12 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private TextView replyTextView;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
private TextView replyContentTextView;
|
2017-06-23 04:59:12 +10:00
|
|
|
|
private EditTextTyped textEditor;
|
|
|
|
|
private LinearLayout mediaPreviewBar;
|
|
|
|
|
private View contentWarningBar;
|
|
|
|
|
private EditText contentWarningEditor;
|
|
|
|
|
private TextView charactersLeft;
|
|
|
|
|
private Button floatingBtn;
|
2017-07-28 12:40:58 +10:00
|
|
|
|
private ImageButton pickButton;
|
2017-06-23 04:59:12 +10:00
|
|
|
|
private ImageButton visibilityBtn;
|
2017-07-27 14:56:53 +10:00
|
|
|
|
private ImageButton saveButton;
|
|
|
|
|
private ImageButton hideMediaToggle;
|
2017-06-23 04:59:12 +10:00
|
|
|
|
private ProgressBar postProgress;
|
|
|
|
|
// this only exists when a status is trying to be sent, but uploads are still occurring
|
|
|
|
|
private ProgressDialog finishingUploadDialog;
|
2017-01-20 15:59:21 +11:00
|
|
|
|
private String inReplyToId;
|
2017-02-18 09:56:31 +11:00
|
|
|
|
private ArrayList<QueuedMedia> mediaQueued;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private CountUpDownLatch waitForMediaLatch;
|
2017-02-04 11:53:33 +11:00
|
|
|
|
private boolean showMarkSensitive;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private Status.Visibility statusVisibility; // The current values of the options that will be applied
|
2017-02-04 11:53:33 +11:00
|
|
|
|
private boolean statusMarkSensitive; // to the status being composed.
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private boolean statusHideText;
|
2017-03-02 17:05:02 +11:00
|
|
|
|
private boolean statusAlreadyInFlight; // to prevent duplicate sends by mashing the send button
|
2017-03-04 12:44:44 +11:00
|
|
|
|
private InputContentInfoCompat currentInputContentInfo;
|
|
|
|
|
private int currentFlags;
|
2017-04-16 01:43:26 +10:00
|
|
|
|
private Uri photoUploadUri;
|
2017-07-07 20:32:47 +10:00
|
|
|
|
private int savedTootUid = 0;
|
2017-06-29 03:33:54 +10:00
|
|
|
|
|
2017-01-08 09:24:02 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
setContentView(R.layout.activity_compose);
|
2017-06-23 04:59:12 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
replyTextView = findViewById(R.id.reply_tv);
|
2017-11-02 06:59:29 +11:00
|
|
|
|
replyContentTextView = findViewById(R.id.reply_content_tv);
|
2017-10-18 09:20:26 +11:00
|
|
|
|
textEditor = findViewById(R.id.compose_edit_field);
|
|
|
|
|
mediaPreviewBar = findViewById(R.id.compose_media_preview_bar);
|
2017-06-23 04:59:12 +10:00
|
|
|
|
contentWarningBar = findViewById(R.id.compose_content_warning_bar);
|
2017-10-18 09:20:26 +11:00
|
|
|
|
contentWarningEditor = findViewById(R.id.field_content_warning);
|
|
|
|
|
charactersLeft = findViewById(R.id.characters_left);
|
|
|
|
|
floatingBtn = findViewById(R.id.floating_btn);
|
|
|
|
|
pickButton = findViewById(R.id.compose_photo_pick);
|
|
|
|
|
visibilityBtn = findViewById(R.id.action_toggle_visibility);
|
|
|
|
|
saveButton = findViewById(R.id.compose_save_draft);
|
|
|
|
|
hideMediaToggle = findViewById(R.id.action_hide_media);
|
|
|
|
|
postProgress = findViewById(R.id.postProgress);
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Setup the toolbar.
|
2017-10-18 09:20:26 +11:00
|
|
|
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
2017-03-08 00:09:33 +11:00
|
|
|
|
setSupportActionBar(toolbar);
|
|
|
|
|
ActionBar actionBar = getSupportActionBar();
|
|
|
|
|
if (actionBar != null) {
|
|
|
|
|
actionBar.setTitle(null);
|
|
|
|
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
|
|
|
|
actionBar.setDisplayShowHomeEnabled(true);
|
2017-04-03 09:10:07 +10:00
|
|
|
|
Drawable closeIcon = AppCompatResources.getDrawable(this, R.drawable.ic_close_24dp);
|
2017-03-29 15:22:14 +11:00
|
|
|
|
ThemeUtils.setDrawableTint(this, closeIcon, R.attr.compose_close_button_tint);
|
|
|
|
|
actionBar.setHomeAsUpIndicator(closeIcon);
|
2017-03-08 00:09:33 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Setup the interface buttons.
|
2017-03-08 00:09:33 +11:00
|
|
|
|
floatingBtn.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
onSendClicked();
|
2017-03-08 00:09:33 +11:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-06-30 01:56:58 +10:00
|
|
|
|
floatingBtn.setOnLongClickListener(new View.OnLongClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onLongClick(View v) {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
return saveDraft();
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-07-28 12:40:58 +10:00
|
|
|
|
pickButton.setOnClickListener(new View.OnClickListener() {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-07-28 12:40:58 +10:00
|
|
|
|
openPickDialog();
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-07-27 14:56:53 +10:00
|
|
|
|
visibilityBtn.setOnClickListener(new View.OnClickListener() {
|
2017-04-16 01:43:26 +10:00
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
showComposeOptions();
|
2017-04-16 01:43:26 +10:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-07-27 14:56:53 +10:00
|
|
|
|
saveButton.setOnClickListener(new View.OnClickListener() {
|
2017-03-11 04:38:49 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
saveDraft();
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-07-27 14:56:53 +10:00
|
|
|
|
hideMediaToggle.setOnClickListener(new View.OnClickListener() {
|
2017-03-11 04:38:49 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
toggleHideMedia();
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-03-08 00:09:33 +11:00
|
|
|
|
|
2017-10-24 07:55:43 +11:00
|
|
|
|
//fix a bug with autocomplete and some keyboards
|
|
|
|
|
int newInputType = textEditor.getInputType() & (textEditor.getInputType() ^ InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
|
|
|
|
|
textEditor.setInputType(newInputType);
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
/* Initialise all the state, or restore it from a previous run, to determine a "starting"
|
|
|
|
|
* state. */
|
|
|
|
|
SharedPreferences preferences = getPrivatePreferences();
|
2017-04-06 06:35:22 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Status.Visibility startingVisibility;
|
2017-03-27 10:37:32 +11:00
|
|
|
|
boolean startingHideText;
|
2017-04-08 04:37:27 +10:00
|
|
|
|
String startingContentWarning = null;
|
2017-02-18 09:56:31 +11:00
|
|
|
|
ArrayList<SavedQueuedMedia> savedMediaQueued = null;
|
|
|
|
|
if (savedInstanceState != null) {
|
|
|
|
|
showMarkSensitive = savedInstanceState.getBoolean("showMarkSensitive");
|
2017-11-17 05:18:11 +11:00
|
|
|
|
startingVisibility = Status.Visibility.byNum(
|
|
|
|
|
savedInstanceState.getInt("statusVisibility",
|
|
|
|
|
Status.Visibility.PUBLIC.getNum())
|
|
|
|
|
);
|
2017-02-18 09:56:31 +11:00
|
|
|
|
statusMarkSensitive = savedInstanceState.getBoolean("statusMarkSensitive");
|
2017-03-27 10:37:32 +11:00
|
|
|
|
startingHideText = savedInstanceState.getBoolean("statusHideText");
|
2017-02-18 09:56:31 +11:00
|
|
|
|
// Keep these until everything needed to put them in the queue is finished initializing.
|
|
|
|
|
savedMediaQueued = savedInstanceState.getParcelableArrayList("savedMediaQueued");
|
2017-03-04 12:44:44 +11:00
|
|
|
|
// These are for restoring an in-progress commit content operation.
|
|
|
|
|
InputContentInfoCompat previousInputContentInfo = InputContentInfoCompat.wrap(
|
|
|
|
|
savedInstanceState.getParcelable("commitContentInputContentInfo"));
|
|
|
|
|
int previousFlags = savedInstanceState.getInt("commitContentFlags");
|
|
|
|
|
if (previousInputContentInfo != null) {
|
|
|
|
|
onCommitContentInternal(previousInputContentInfo, previousFlags);
|
|
|
|
|
}
|
2017-07-03 07:45:13 +10:00
|
|
|
|
photoUploadUri = savedInstanceState.getParcelable("photoUploadUri");
|
2017-02-18 09:56:31 +11:00
|
|
|
|
} else {
|
|
|
|
|
showMarkSensitive = false;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
startingVisibility = Status.Visibility.byNum(
|
|
|
|
|
preferences.getInt(REMEMBERED_VISIBILITY_PREF,
|
|
|
|
|
Status.Visibility.UNKNOWN.getNum())
|
|
|
|
|
);
|
2017-02-18 09:56:31 +11:00
|
|
|
|
statusMarkSensitive = false;
|
2017-03-27 10:37:32 +11:00
|
|
|
|
startingHideText = false;
|
2017-07-03 07:45:13 +10:00
|
|
|
|
photoUploadUri = null;
|
2017-02-18 09:56:31 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
/* If the composer is started up as a reply to another post, override the "starting" state
|
|
|
|
|
* based on what the intent from the reply request passes. */
|
|
|
|
|
Intent intent = getIntent();
|
2017-03-11 04:38:49 +11:00
|
|
|
|
|
2017-01-20 15:59:21 +11:00
|
|
|
|
String[] mentionedUsernames = null;
|
2017-07-08 12:56:07 +10:00
|
|
|
|
ArrayList<String> loadedDraftMediaUris = null;
|
2017-03-27 08:21:18 +11:00
|
|
|
|
inReplyToId = null;
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Status.Visibility replyVisibility = Status.Visibility.UNKNOWN;
|
2017-01-20 15:59:21 +11:00
|
|
|
|
if (intent != null) {
|
2017-11-02 06:59:29 +11:00
|
|
|
|
inReplyToId = intent.getStringExtra(IN_REPLY_TO_ID_EXTRA);
|
2017-11-17 05:18:11 +11:00
|
|
|
|
replyVisibility = Status.Visibility.byNum(
|
|
|
|
|
intent.getIntExtra(REPLY_VISIBILITY_EXTRA, Status.Visibility.UNKNOWN.getNum())
|
|
|
|
|
);
|
2017-03-15 00:31:52 +11:00
|
|
|
|
|
2017-11-02 06:59:29 +11:00
|
|
|
|
mentionedUsernames = intent.getStringArrayExtra(MENTIONED_USERNAMES_EXTRA);
|
2017-04-08 04:37:27 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
String contentWarning = intent.getStringExtra(CONTENT_WARNING_EXTRA);
|
|
|
|
|
if (contentWarning != null) {
|
|
|
|
|
startingHideText = !contentWarning.isEmpty();
|
2017-05-03 08:17:54 +10:00
|
|
|
|
if (startingHideText) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
startingContentWarning = contentWarning;
|
2017-07-08 12:56:07 +10:00
|
|
|
|
}
|
2017-04-08 04:37:27 +10:00
|
|
|
|
}
|
2017-07-06 00:36:14 +10:00
|
|
|
|
|
2017-07-08 12:56:07 +10:00
|
|
|
|
// If come from SavedTootActivity
|
2017-11-02 06:59:29 +11:00
|
|
|
|
String savedTootText = intent.getStringExtra(SAVED_TOOT_TEXT_EXTRA);
|
2017-07-06 00:36:14 +10:00
|
|
|
|
if (!TextUtils.isEmpty(savedTootText)) {
|
|
|
|
|
textEditor.append(savedTootText);
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-02 06:59:29 +11:00
|
|
|
|
String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA);
|
2017-07-06 00:36:14 +10:00
|
|
|
|
if (!TextUtils.isEmpty(savedJsonUrls)) {
|
|
|
|
|
// try to redo a list of media
|
2017-07-08 12:56:07 +10:00
|
|
|
|
loadedDraftMediaUris = new Gson().fromJson(savedJsonUrls,
|
2017-10-30 08:18:45 +11:00
|
|
|
|
new TypeToken<ArrayList<String>>() {
|
|
|
|
|
}.getType());
|
2017-07-07 20:32:47 +10:00
|
|
|
|
}
|
2017-07-06 00:36:14 +10:00
|
|
|
|
|
2017-11-02 06:59:29 +11:00
|
|
|
|
int savedTootUid = intent.getIntExtra(SAVED_TOOT_UID_EXTRA, 0);
|
2017-07-07 20:32:47 +10:00
|
|
|
|
if (savedTootUid != 0) {
|
|
|
|
|
this.savedTootUid = savedTootUid;
|
2017-07-06 00:36:14 +10:00
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
|
|
|
|
|
if (intent.hasExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA)) {
|
|
|
|
|
replyTextView.setVisibility(View.VISIBLE);
|
|
|
|
|
String username = intent.getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA);
|
|
|
|
|
replyTextView.setText(getString(R.string.replying_to, username));
|
|
|
|
|
replyTextView.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
if (replyContentTextView.getVisibility() != View.VISIBLE) {
|
|
|
|
|
replyContentTextView.setVisibility(View.VISIBLE);
|
|
|
|
|
} else {
|
|
|
|
|
replyContentTextView.setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (intent.hasExtra(REPLYING_STATUS_CONTENT_EXTRA)) {
|
|
|
|
|
replyContentTextView.setText(intent.getStringExtra(REPLYING_STATUS_CONTENT_EXTRA));
|
|
|
|
|
}
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
2017-05-03 08:17:54 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Status.Visibility pickedVisibility = pickVisibility(startingVisibility, replyVisibility,
|
|
|
|
|
preferences.getBoolean("loggedInAccountLocked", false));
|
2017-05-03 08:17:54 +10:00
|
|
|
|
|
|
|
|
|
// After the starting state is finalised, the interface can be set to reflect this state.
|
2017-11-17 05:18:11 +11:00
|
|
|
|
setStatusVisibility(pickedVisibility);
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
postProgress.setVisibility(View.INVISIBLE);
|
2017-06-21 04:16:03 +10:00
|
|
|
|
updateHideMediaToggleColor();
|
2017-07-15 08:18:29 +10:00
|
|
|
|
updateVisibleCharactersLeft();
|
2017-01-20 15:59:21 +11:00
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Setup the main text field.
|
2017-11-02 07:56:38 +11:00
|
|
|
|
setEditTextMimeTypes(); // new String[] { "image/gif", "image/webp" }
|
2017-04-05 06:02:08 +10:00
|
|
|
|
final int mentionColour = ThemeUtils.getColor(this, R.attr.compose_mention_color);
|
2017-05-03 08:17:54 +10:00
|
|
|
|
SpanUtils.highlightSpans(textEditor.getText(), mentionColour);
|
2017-03-21 23:52:15 +11:00
|
|
|
|
textEditor.addTextChangedListener(new TextWatcher() {
|
2017-01-08 09:24:02 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
2017-04-11 03:34:33 +10:00
|
|
|
|
updateVisibleCharactersLeft();
|
2017-01-08 09:24:02 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-05-16 18:43:32 +10:00
|
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
|
|
|
}
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
|
|
|
|
@Override
|
2017-01-20 15:59:21 +11:00
|
|
|
|
public void afterTextChanged(Editable editable) {
|
2017-05-03 08:17:54 +10:00
|
|
|
|
SpanUtils.highlightSpans(editable, mentionColour);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
2017-03-21 23:52:15 +11:00
|
|
|
|
});
|
2017-01-08 09:24:02 +11:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
textEditor.setAdapter(
|
|
|
|
|
new MentionAutoCompleteAdapter(this, R.layout.item_autocomplete, this));
|
2017-06-19 12:10:50 +10:00
|
|
|
|
textEditor.setTokenizer(new MentionTokenizer());
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Add any mentions to the text field when a reply is first composed.
|
2017-01-20 15:59:21 +11:00
|
|
|
|
if (mentionedUsernames != null) {
|
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
|
for (String name : mentionedUsernames) {
|
|
|
|
|
builder.append('@');
|
|
|
|
|
builder.append(name);
|
|
|
|
|
builder.append(' ');
|
|
|
|
|
}
|
|
|
|
|
textEditor.setText(builder);
|
|
|
|
|
textEditor.setSelection(textEditor.length());
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Initialise the content warning editor.
|
2017-03-20 13:38:39 +11:00
|
|
|
|
contentWarningEditor.addTextChangedListener(new TextWatcher() {
|
|
|
|
|
@Override
|
2017-05-16 18:43:32 +10:00
|
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
|
|
|
}
|
2017-03-20 13:38:39 +11:00
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
2017-04-11 03:34:33 +10:00
|
|
|
|
updateVisibleCharactersLeft();
|
2017-03-20 13:38:39 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-05-16 18:43:32 +10:00
|
|
|
|
public void afterTextChanged(Editable s) {
|
|
|
|
|
}
|
2017-03-20 13:38:39 +11:00
|
|
|
|
});
|
2017-03-27 10:37:32 +11:00
|
|
|
|
showContentWarning(startingHideText);
|
2017-05-16 18:43:32 +10:00
|
|
|
|
if (startingContentWarning != null) {
|
2017-04-08 04:37:27 +10:00
|
|
|
|
contentWarningEditor.setText(startingContentWarning);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
// Initialise the empty media queue state.
|
|
|
|
|
mediaQueued = new ArrayList<>();
|
|
|
|
|
waitForMediaLatch = new CountUpDownLatch();
|
2017-03-02 17:05:02 +11:00
|
|
|
|
statusAlreadyInFlight = false;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
|
2017-03-08 00:09:33 +11:00
|
|
|
|
// These can only be added after everything affected by the media queue is initialized.
|
2017-10-30 08:18:45 +11:00
|
|
|
|
if (!ListUtils.isEmpty(loadedDraftMediaUris)) {
|
2017-07-08 12:56:07 +10:00
|
|
|
|
for (String uriString : loadedDraftMediaUris) {
|
|
|
|
|
Uri uri = Uri.parse(uriString);
|
|
|
|
|
long mediaSize = MediaUtils.getMediaSize(getContentResolver(), uri);
|
|
|
|
|
pickMedia(uri, mediaSize);
|
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
} else if (savedMediaQueued != null) {
|
2017-03-08 00:09:33 +11:00
|
|
|
|
for (SavedQueuedMedia item : savedMediaQueued) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Bitmap preview = MediaUtils.getImageThumbnail(getContentResolver(), item.uri, THUMBNAIL_SIZE);
|
2017-10-29 07:24:26 +11:00
|
|
|
|
addMediaToQueue(item.type, preview, item.uri, item.mediaSize, item.readyStage);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-04-06 15:11:44 +10:00
|
|
|
|
} else if (intent != null && savedInstanceState == null) {
|
2017-04-06 06:35:22 +10:00
|
|
|
|
/* Get incoming images being sent through a share action from another app. Only do this
|
|
|
|
|
* when savedInstanceState is null, otherwise both the images from the intent and the
|
|
|
|
|
* instance state will be re-queued. */
|
|
|
|
|
String type = intent.getType();
|
2017-04-06 15:11:44 +10:00
|
|
|
|
if (type != null) {
|
|
|
|
|
if (type.startsWith("image/")) {
|
|
|
|
|
List<Uri> uriList = new ArrayList<>();
|
2017-11-17 05:18:11 +11:00
|
|
|
|
if (intent.getAction() != null) {
|
|
|
|
|
switch (intent.getAction()) {
|
|
|
|
|
case Intent.ACTION_SEND: {
|
|
|
|
|
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
|
|
|
|
if (uri != null) {
|
|
|
|
|
uriList.add(uri);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2017-04-06 15:11:44 +10:00
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
case Intent.ACTION_SEND_MULTIPLE: {
|
|
|
|
|
ArrayList<Uri> list = intent.getParcelableArrayListExtra(
|
|
|
|
|
Intent.EXTRA_STREAM);
|
|
|
|
|
if (list != null) {
|
|
|
|
|
for (Uri uri : list) {
|
|
|
|
|
if (uri != null) {
|
|
|
|
|
uriList.add(uri);
|
|
|
|
|
}
|
2017-04-06 15:11:44 +10:00
|
|
|
|
}
|
2017-04-06 06:35:22 +10:00
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
break;
|
2017-04-06 06:35:22 +10:00
|
|
|
|
}
|
2017-04-06 15:11:44 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (Uri uri : uriList) {
|
2017-07-03 06:34:10 +10:00
|
|
|
|
long mediaSize = MediaUtils.getMediaSize(getContentResolver(), uri);
|
2017-04-06 15:11:44 +10:00
|
|
|
|
pickMedia(uri, mediaSize);
|
|
|
|
|
}
|
|
|
|
|
} else if (type.equals("text/plain")) {
|
|
|
|
|
String action = intent.getAction();
|
|
|
|
|
if (action != null && action.equals(Intent.ACTION_SEND)) {
|
|
|
|
|
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
|
|
|
|
if (text != null) {
|
|
|
|
|
int start = Math.max(textEditor.getSelectionStart(), 0);
|
|
|
|
|
int end = Math.max(textEditor.getSelectionEnd(), 0);
|
|
|
|
|
int left = Math.min(start, end);
|
|
|
|
|
int right = Math.max(start, end);
|
|
|
|
|
textEditor.getText().replace(left, right, text, 0, text.length());
|
2017-04-06 06:35:22 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-08 00:09:33 +11:00
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
|
|
|
|
|
|
2017-03-08 00:09:33 +11:00
|
|
|
|
}
|
2017-01-17 05:15:42 +11:00
|
|
|
|
|
2017-05-03 08:17:54 +10:00
|
|
|
|
@Override
|
|
|
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
|
|
|
ArrayList<SavedQueuedMedia> savedMediaQueued = new ArrayList<>();
|
|
|
|
|
for (QueuedMedia item : mediaQueued) {
|
2017-10-29 07:24:26 +11:00
|
|
|
|
savedMediaQueued.add(new SavedQueuedMedia(item.type, item.uri,
|
|
|
|
|
item.mediaSize, item.readyStage));
|
2017-05-03 08:17:54 +10:00
|
|
|
|
}
|
|
|
|
|
outState.putParcelableArrayList("savedMediaQueued", savedMediaQueued);
|
|
|
|
|
outState.putBoolean("showMarkSensitive", showMarkSensitive);
|
|
|
|
|
outState.putBoolean("statusMarkSensitive", statusMarkSensitive);
|
|
|
|
|
outState.putBoolean("statusHideText", statusHideText);
|
|
|
|
|
if (currentInputContentInfo != null) {
|
|
|
|
|
outState.putParcelable("commitContentInputContentInfo",
|
|
|
|
|
(Parcelable) currentInputContentInfo.unwrap());
|
|
|
|
|
outState.putInt("commitContentFlags", currentFlags);
|
|
|
|
|
}
|
|
|
|
|
currentInputContentInfo = null;
|
|
|
|
|
currentFlags = 0;
|
2017-07-03 07:45:13 +10:00
|
|
|
|
outState.putParcelable("photoUploadUri", photoUploadUri);
|
2017-05-03 08:17:54 +10:00
|
|
|
|
super.onSaveInstanceState(outState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
|
2017-07-31 07:16:32 +10:00
|
|
|
|
View.OnClickListener listener) {
|
2017-05-03 08:17:54 +10:00
|
|
|
|
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(descriptionId),
|
|
|
|
|
Snackbar.LENGTH_SHORT);
|
|
|
|
|
bar.setAction(actionId, listener);
|
|
|
|
|
bar.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void displayTransientError(@StringRes int stringId) {
|
|
|
|
|
Snackbar.make(findViewById(R.id.activity_compose), stringId, Snackbar.LENGTH_LONG).show();
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-21 04:16:03 +10:00
|
|
|
|
private void toggleHideMedia() {
|
2017-03-11 04:38:49 +11:00
|
|
|
|
statusMarkSensitive = !statusMarkSensitive;
|
2017-06-21 04:16:03 +10:00
|
|
|
|
updateHideMediaToggleColor();
|
2017-03-29 15:22:14 +11:00
|
|
|
|
}
|
2017-03-11 04:38:49 +11:00
|
|
|
|
|
2017-06-21 04:16:03 +10:00
|
|
|
|
private void updateHideMediaToggleColor() {
|
2017-05-03 08:17:54 +10:00
|
|
|
|
@AttrRes int attribute;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
if (statusMarkSensitive) {
|
2017-06-21 04:16:03 +10:00
|
|
|
|
attribute = R.attr.compose_hide_media_button_selected_color;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
} else {
|
2017-06-21 04:16:03 +10:00
|
|
|
|
attribute = R.attr.compose_hide_media_button_color;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
2017-06-21 04:16:03 +10:00
|
|
|
|
ThemeUtils.setDrawableTint(this, hideMediaToggle.getDrawable(), attribute);
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-16 01:52:21 +10:00
|
|
|
|
private void disableButtons() {
|
2017-07-28 12:40:58 +10:00
|
|
|
|
pickButton.setClickable(false);
|
2017-04-16 01:52:21 +10:00
|
|
|
|
visibilityBtn.setClickable(false);
|
2017-07-27 14:56:53 +10:00
|
|
|
|
saveButton.setClickable(false);
|
|
|
|
|
hideMediaToggle.setClickable(false);
|
2017-04-16 01:52:21 +10:00
|
|
|
|
floatingBtn.setEnabled(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void enableButtons() {
|
2017-07-28 12:40:58 +10:00
|
|
|
|
pickButton.setClickable(true);
|
2017-04-16 01:52:21 +10:00
|
|
|
|
visibilityBtn.setClickable(true);
|
2017-07-27 14:56:53 +10:00
|
|
|
|
saveButton.setClickable(true);
|
|
|
|
|
hideMediaToggle.setClickable(true);
|
2017-04-16 01:52:21 +10:00
|
|
|
|
floatingBtn.setEnabled(true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-27 14:56:53 +10:00
|
|
|
|
private boolean saveDraft() {
|
|
|
|
|
String contentWarning = null;
|
|
|
|
|
if (statusHideText) {
|
|
|
|
|
contentWarning = contentWarningEditor.getText().toString();
|
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
Editable textToSave = textEditor.getEditableText();
|
2017-07-27 14:56:53 +10:00
|
|
|
|
/* Discard any upload URLs embedded in the text because they'll be re-uploaded when
|
|
|
|
|
* the draft is loaded and replaced with new URLs. */
|
|
|
|
|
if (mediaQueued != null) {
|
|
|
|
|
for (QueuedMedia item : mediaQueued) {
|
2017-11-02 06:59:29 +11:00
|
|
|
|
textToSave = removeUrlFromEditable(textToSave, item.uploadUrl);
|
2017-07-27 14:56:53 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
boolean didSaveSuccessfully = saveTheToot(textToSave.toString(), contentWarning);
|
|
|
|
|
if (didSaveSuccessfully) {
|
2017-07-27 14:56:53 +10:00
|
|
|
|
Toast.makeText(ComposeActivity.this, R.string.action_save_one_toot, Toast.LENGTH_SHORT)
|
|
|
|
|
.show();
|
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
return didSaveSuccessfully;
|
2017-07-27 14:56:53 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-12 11:49:46 +10:00
|
|
|
|
private static boolean copyToFile(ContentResolver contentResolver, Uri uri, File file) {
|
|
|
|
|
InputStream from;
|
|
|
|
|
FileOutputStream to;
|
|
|
|
|
try {
|
|
|
|
|
from = contentResolver.openInputStream(uri);
|
|
|
|
|
to = new FileOutputStream(file);
|
|
|
|
|
} catch (FileNotFoundException e) {
|
2017-06-30 01:56:58 +10:00
|
|
|
|
return false;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
|
|
|
|
if (from == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
byte[] chunk = new byte[16384];
|
|
|
|
|
try {
|
|
|
|
|
while (true) {
|
|
|
|
|
int bytes = from.read(chunk, 0, chunk.length);
|
|
|
|
|
if (bytes < 0) {
|
|
|
|
|
break;
|
2017-07-06 00:36:14 +10:00
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
to.write(chunk, 0, bytes);
|
2017-07-06 00:36:14 +10:00
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
IOUtils.closeQuietly(from);
|
|
|
|
|
IOUtils.closeQuietly(to);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-07-06 00:36:14 +10:00
|
|
|
|
|
2017-07-12 11:49:46 +10:00
|
|
|
|
@Nullable
|
|
|
|
|
private List<String> saveMedia(@Nullable ArrayList<String> existingUris) {
|
|
|
|
|
File imageDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
|
|
|
|
File videoDirectory = getExternalFilesDir(Environment.DIRECTORY_MOVIES);
|
|
|
|
|
if (imageDirectory == null || !(imageDirectory.exists() || imageDirectory.mkdirs())) {
|
|
|
|
|
Log.e(TAG, "Image directory is not created.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (videoDirectory == null || !(videoDirectory.exists() || videoDirectory.mkdirs())) {
|
|
|
|
|
Log.e(TAG, "Video directory is not created.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
ContentResolver contentResolver = getContentResolver();
|
|
|
|
|
ArrayList<File> filesSoFar = new ArrayList<>();
|
|
|
|
|
ArrayList<String> results = new ArrayList<>();
|
|
|
|
|
for (QueuedMedia item : mediaQueued) {
|
|
|
|
|
/* If the media was already saved in a previous draft, there's no need to save another
|
|
|
|
|
* copy, just add the existing URI to the results. */
|
|
|
|
|
if (existingUris != null) {
|
|
|
|
|
String uri = item.uri.toString();
|
|
|
|
|
int index = existingUris.indexOf(uri);
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
results.add(uri);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, save the media.
|
|
|
|
|
File directory;
|
|
|
|
|
switch (item.type) {
|
|
|
|
|
default:
|
2017-10-29 07:24:26 +11:00
|
|
|
|
case IMAGE:
|
|
|
|
|
directory = imageDirectory;
|
|
|
|
|
break;
|
|
|
|
|
case VIDEO:
|
|
|
|
|
directory = videoDirectory;
|
|
|
|
|
break;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
|
|
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)
|
|
|
|
|
.format(new Date());
|
|
|
|
|
String mimeType = contentResolver.getType(item.uri);
|
|
|
|
|
MimeTypeMap map = MimeTypeMap.getSingleton();
|
|
|
|
|
String fileExtension = map.getExtensionFromMimeType(mimeType);
|
|
|
|
|
String filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension);
|
|
|
|
|
File file = new File(directory, filename);
|
|
|
|
|
filesSoFar.add(file);
|
|
|
|
|
boolean copied = copyToFile(contentResolver, item.uri, file);
|
|
|
|
|
if (!copied) {
|
|
|
|
|
/* If any media files were created in prior iterations, delete those before
|
|
|
|
|
* returning. */
|
|
|
|
|
for (File earlierFile : filesSoFar) {
|
|
|
|
|
boolean deleted = earlierFile.delete();
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
Log.i(TAG, "Could not delete the file " + earlierFile.toString());
|
2017-07-07 20:32:47 +10:00
|
|
|
|
}
|
2017-06-30 01:56:58 +10:00
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Uri uri = FileProvider.getUriForFile(this, "com.keylesspalace.tusky.fileprovider",
|
|
|
|
|
file);
|
|
|
|
|
results.add(uri.toString());
|
|
|
|
|
}
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void deleteMedia(List<String> mediaUris) {
|
|
|
|
|
for (String uriString : mediaUris) {
|
|
|
|
|
Uri uri = Uri.parse(uriString);
|
|
|
|
|
if (getContentResolver().delete(uri, null, null) == 0) {
|
|
|
|
|
Log.e(TAG, String.format("Did not delete file %s.", uriString));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-21 12:29:29 +10:00
|
|
|
|
/**
|
|
|
|
|
* A∖B={x∈A|x∉B}
|
2017-10-30 08:18:45 +11:00
|
|
|
|
*
|
2017-07-21 12:29:29 +10:00
|
|
|
|
* @return all elements of set A that are not in set B.
|
|
|
|
|
*/
|
2017-07-12 11:49:46 +10:00
|
|
|
|
private static List<String> setDifference(List<String> a, List<String> b) {
|
|
|
|
|
List<String> c = new ArrayList<>();
|
|
|
|
|
for (String s : a) {
|
|
|
|
|
if (!b.contains(s)) {
|
|
|
|
|
c.add(s);
|
|
|
|
|
}
|
2017-06-30 01:56:58 +10:00
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
2017-10-18 09:20:26 +11:00
|
|
|
|
private boolean saveTheToot(String s, @Nullable String contentWarning) {
|
2017-07-12 11:49:46 +10:00
|
|
|
|
if (TextUtils.isEmpty(s)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get any existing file's URIs.
|
|
|
|
|
ArrayList<String> existingUris = null;
|
|
|
|
|
String savedJsonUrls = getIntent().getStringExtra("saved_json_urls");
|
|
|
|
|
if (!TextUtils.isEmpty(savedJsonUrls)) {
|
|
|
|
|
existingUris = new Gson().fromJson(savedJsonUrls,
|
2017-10-30 08:18:45 +11:00
|
|
|
|
new TypeToken<ArrayList<String>>() {
|
|
|
|
|
}.getType());
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
String mediaUrlsSerialized = null;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
if (!ListUtils.isEmpty(mediaQueued)) {
|
|
|
|
|
List<String> savedList = saveMedia(existingUris);
|
|
|
|
|
if (!ListUtils.isEmpty(savedList)) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
mediaUrlsSerialized = new Gson().toJson(savedList);
|
2017-10-30 08:18:45 +11:00
|
|
|
|
if (!ListUtils.isEmpty(existingUris)) {
|
2017-10-17 18:37:59 +11:00
|
|
|
|
deleteMedia(setDifference(existingUris, savedList));
|
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else if (!ListUtils.isEmpty(existingUris)) {
|
|
|
|
|
/* If there were URIs in the previous draft, but they've now been removed, those files
|
|
|
|
|
* can be deleted. */
|
|
|
|
|
deleteMedia(existingUris);
|
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
final TootEntity toot = new TootEntity(savedTootUid, s, mediaUrlsSerialized, contentWarning,
|
|
|
|
|
inReplyToId,
|
|
|
|
|
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
|
|
|
|
|
getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA), statusVisibility);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
|
|
|
|
|
new AsyncTask<Void, Void, Void>() {
|
|
|
|
|
@Override
|
|
|
|
|
protected Void doInBackground(Void... params) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
tootDao.insertOrReplace(toot);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}.execute();
|
|
|
|
|
return true;
|
2017-06-30 01:56:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-12 16:09:07 +10:00
|
|
|
|
private void addLockToSendButton() {
|
|
|
|
|
floatingBtn.setText(R.string.action_send);
|
|
|
|
|
Drawable lock = AppCompatResources.getDrawable(this, R.drawable.send_private);
|
|
|
|
|
if (lock != null) {
|
|
|
|
|
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
|
|
|
|
floatingBtn.setCompoundDrawables(null, null, lock, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void setStatusVisibility(Status.Visibility visibility) {
|
2017-03-27 09:26:47 +11:00
|
|
|
|
statusVisibility = visibility;
|
|
|
|
|
switch (visibility) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
case PUBLIC: {
|
2017-03-27 09:26:47 +11:00
|
|
|
|
floatingBtn.setText(R.string.action_send_public);
|
|
|
|
|
floatingBtn.setCompoundDrawables(null, null, null, null);
|
2017-04-21 15:50:59 +10:00
|
|
|
|
Drawable globe = AppCompatResources.getDrawable(this, R.drawable.ic_public_24dp);
|
|
|
|
|
if (globe != null) {
|
|
|
|
|
visibilityBtn.setImageDrawable(globe);
|
|
|
|
|
}
|
2017-03-27 09:26:47 +11:00
|
|
|
|
break;
|
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
case PRIVATE: {
|
2017-04-21 15:50:59 +10:00
|
|
|
|
addLockToSendButton();
|
|
|
|
|
Drawable lock = AppCompatResources.getDrawable(this,
|
|
|
|
|
R.drawable.ic_lock_outline_24dp);
|
2017-04-03 09:10:07 +10:00
|
|
|
|
if (lock != null) {
|
2017-04-21 15:50:59 +10:00
|
|
|
|
visibilityBtn.setImageDrawable(lock);
|
2017-04-03 09:10:07 +10:00
|
|
|
|
}
|
2017-03-27 09:26:47 +11:00
|
|
|
|
break;
|
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
case DIRECT: {
|
2017-04-21 15:50:59 +10:00
|
|
|
|
addLockToSendButton();
|
|
|
|
|
Drawable envelope = AppCompatResources.getDrawable(this, R.drawable.ic_email_24dp);
|
|
|
|
|
if (envelope != null) {
|
|
|
|
|
visibilityBtn.setImageDrawable(envelope);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
case UNLISTED:
|
2017-03-27 09:26:47 +11:00
|
|
|
|
default: {
|
|
|
|
|
floatingBtn.setText(R.string.action_send);
|
|
|
|
|
floatingBtn.setCompoundDrawables(null, null, null, null);
|
2017-04-21 15:50:59 +10:00
|
|
|
|
Drawable openLock = AppCompatResources.getDrawable(this,
|
|
|
|
|
R.drawable.ic_lock_open_24dp);
|
|
|
|
|
if (openLock != null) {
|
|
|
|
|
visibilityBtn.setImageDrawable(openLock);
|
|
|
|
|
}
|
2017-03-27 09:26:47 +11:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-08 00:09:33 +11:00
|
|
|
|
private void showComposeOptions() {
|
|
|
|
|
ComposeOptionsFragment fragment = ComposeOptionsFragment.newInstance(
|
2017-06-27 05:46:03 +10:00
|
|
|
|
statusVisibility, statusHideText);
|
2017-03-08 00:09:33 +11:00
|
|
|
|
fragment.show(getSupportFragmentManager(), null);
|
|
|
|
|
}
|
2017-02-18 09:56:31 +11:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onVisibilityChanged(Status.Visibility visibility) {
|
2017-04-05 12:29:15 +10:00
|
|
|
|
setStatusVisibility(visibility);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-11 03:34:33 +10:00
|
|
|
|
private void updateVisibleCharactersLeft() {
|
|
|
|
|
int left = STATUS_CHARACTER_LIMIT - textEditor.length();
|
|
|
|
|
if (statusHideText) {
|
|
|
|
|
left -= contentWarningEditor.length();
|
|
|
|
|
}
|
|
|
|
|
charactersLeft.setText(String.format(Locale.getDefault(), "%d", left));
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-05 12:29:15 +10:00
|
|
|
|
public void onContentWarningChanged(boolean hideText) {
|
|
|
|
|
showContentWarning(hideText);
|
2017-04-11 03:34:33 +10:00
|
|
|
|
updateVisibleCharactersLeft();
|
2017-04-05 12:29:15 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 09:20:26 +11:00
|
|
|
|
private void setStateToReadying() {
|
2017-04-17 14:29:47 +10:00
|
|
|
|
statusAlreadyInFlight = true;
|
|
|
|
|
disableButtons();
|
|
|
|
|
postProgress.setVisibility(View.VISIBLE);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 09:20:26 +11:00
|
|
|
|
private void setStateToNotReadying() {
|
2017-04-17 14:29:47 +10:00
|
|
|
|
postProgress.setVisibility(View.INVISIBLE);
|
|
|
|
|
statusAlreadyInFlight = false;
|
|
|
|
|
enableButtons();
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-04 10:27:59 +10:00
|
|
|
|
private void onSendClicked() {
|
2017-03-08 00:09:33 +11:00
|
|
|
|
if (statusAlreadyInFlight) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-04 10:27:59 +10:00
|
|
|
|
setStateToReadying();
|
|
|
|
|
readyStatus(statusVisibility, statusMarkSensitive);
|
2017-02-18 09:56:31 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-03-29 04:58:37 +11:00
|
|
|
|
protected void onStop() {
|
|
|
|
|
super.onStop();
|
2017-11-17 05:18:11 +11:00
|
|
|
|
// Don't save the visibility setting for replies because they adopt the visibility of
|
|
|
|
|
// the status they reply to and that behaviour needs to be kept separate.
|
|
|
|
|
if (inReplyToId == null) {
|
|
|
|
|
getPrivatePreferences().edit()
|
|
|
|
|
.putInt(REMEMBERED_VISIBILITY_PREF, statusVisibility.getNum())
|
|
|
|
|
.apply();
|
2017-03-06 11:58:36 +11:00
|
|
|
|
}
|
2017-02-18 09:56:31 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-02 07:56:38 +11:00
|
|
|
|
private void setEditTextMimeTypes() {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
final String[] mimeTypes = new String[]{"image/*"};
|
2017-05-03 08:17:54 +10:00
|
|
|
|
textEditor.setMimeTypes(mimeTypes, new InputConnectionCompat.OnCommitContentListener() {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
@Override
|
2017-05-03 08:17:54 +10:00
|
|
|
|
public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
|
|
|
|
|
int flags, Bundle opts) {
|
|
|
|
|
return ComposeActivity.this.onCommitContent(inputContentInfo, flags,
|
|
|
|
|
mimeTypes);
|
2017-03-04 12:44:44 +11:00
|
|
|
|
}
|
2017-05-03 08:17:54 +10:00
|
|
|
|
});
|
2017-03-04 12:44:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
|
2017-10-30 08:18:45 +11:00
|
|
|
|
String[] mimeTypes) {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
try {
|
|
|
|
|
if (currentInputContentInfo != null) {
|
|
|
|
|
currentInputContentInfo.releasePermission();
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "InputContentInfoCompat#releasePermission() failed." + e.getMessage());
|
|
|
|
|
} finally {
|
|
|
|
|
currentInputContentInfo = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the returned content's type is actually in the list of MIME types requested.
|
|
|
|
|
boolean supported = false;
|
|
|
|
|
for (final String mimeType : mimeTypes) {
|
|
|
|
|
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
|
|
|
|
|
supported = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return supported && onCommitContentInternal(inputContentInfo, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean onCommitContentInternal(InputContentInfoCompat inputContentInfo, int flags) {
|
|
|
|
|
if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
|
|
|
|
|
try {
|
|
|
|
|
inputContentInfo.requestPermission();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine the file size before putting handing it off to be put in the queue.
|
|
|
|
|
Uri uri = inputContentInfo.getContentUri();
|
|
|
|
|
long mediaSize;
|
|
|
|
|
AssetFileDescriptor descriptor = null;
|
|
|
|
|
try {
|
|
|
|
|
descriptor = getContentResolver().openAssetFileDescriptor(uri, "r");
|
|
|
|
|
} catch (FileNotFoundException e) {
|
2017-05-26 01:21:11 +10:00
|
|
|
|
Log.d(TAG, Log.getStackTraceString(e));
|
2017-03-04 12:44:44 +11:00
|
|
|
|
// Eat this exception, having the descriptor be null is sufficient.
|
|
|
|
|
}
|
|
|
|
|
if (descriptor != null) {
|
|
|
|
|
mediaSize = descriptor.getLength();
|
|
|
|
|
try {
|
|
|
|
|
descriptor.close();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
// Just eat this exception.
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2017-07-03 06:34:10 +10:00
|
|
|
|
mediaSize = MediaUtils.MEDIA_SIZE_UNKNOWN;
|
2017-03-04 12:44:44 +11:00
|
|
|
|
}
|
|
|
|
|
pickMedia(uri, mediaSize);
|
|
|
|
|
|
|
|
|
|
currentInputContentInfo = inputContentInfo;
|
|
|
|
|
currentFlags = flags;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void sendStatus(String content, Status.Visibility visibility, boolean sensitive,
|
2017-10-30 08:18:45 +11:00
|
|
|
|
String spoilerText) {
|
2017-03-14 11:49:12 +11:00
|
|
|
|
ArrayList<String> mediaIds = new ArrayList<>();
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
|
|
|
|
for (QueuedMedia item : mediaQueued) {
|
|
|
|
|
mediaIds.add(item.id);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
2017-06-21 06:57:04 +10:00
|
|
|
|
Callback<Status> callback = new Callback<Status>() {
|
2017-01-20 15:59:21 +11:00
|
|
|
|
@Override
|
2017-11-02 07:56:38 +11:00
|
|
|
|
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
2017-03-14 11:49:12 +11:00
|
|
|
|
if (response.isSuccessful()) {
|
|
|
|
|
onSendSuccess();
|
|
|
|
|
} else {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
onSendFailure(response);
|
2017-03-14 11:49:12 +11:00
|
|
|
|
}
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
|
|
|
|
@Override
|
2017-11-02 07:56:38 +11:00
|
|
|
|
public void onFailure(@NonNull Call<Status> call, @NonNull Throwable t) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
onSendFailure(null);
|
2017-03-10 02:59:18 +11:00
|
|
|
|
}
|
2017-06-21 06:57:04 +10:00
|
|
|
|
};
|
2017-11-17 05:18:11 +11:00
|
|
|
|
mastodonApi.createStatus(content, inReplyToId, spoilerText, visibility.serverString(),
|
|
|
|
|
sensitive, mediaIds).enqueue(callback);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-04 11:53:33 +11:00
|
|
|
|
private void onSendSuccess() {
|
2017-07-12 11:49:46 +10:00
|
|
|
|
// If the status was loaded from a draft, delete the draft and associated media files.
|
|
|
|
|
if (savedTootUid != 0) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
tootDao.delete(savedTootUid);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
for (QueuedMedia item : mediaQueued) {
|
2017-11-03 07:18:39 +11:00
|
|
|
|
try {
|
|
|
|
|
if (getContentResolver().delete(item.uri, null, null) == 0) {
|
|
|
|
|
Log.e(TAG, String.format("Did not delete file %s.", item.uri.toString()));
|
|
|
|
|
}
|
|
|
|
|
} catch (SecurityException e) {
|
|
|
|
|
Log.e(TAG, String.format("Did not delete file %s.", item.uri.toString()), e);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
2017-11-03 07:18:39 +11:00
|
|
|
|
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-21 06:57:04 +10:00
|
|
|
|
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose),
|
|
|
|
|
getString(R.string.confirmation_send), Snackbar.LENGTH_SHORT);
|
2017-03-10 02:59:18 +11:00
|
|
|
|
bar.show();
|
2017-04-16 03:44:29 +10:00
|
|
|
|
setResult(COMPOSE_SUCCESS);
|
2017-02-04 11:53:33 +11:00
|
|
|
|
finish();
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void onSendFailure(@Nullable Response<Status> response) {
|
2017-04-17 14:29:47 +10:00
|
|
|
|
setStateToNotReadying();
|
2017-11-17 05:18:11 +11:00
|
|
|
|
|
|
|
|
|
if (response != null && inReplyToId != null && response.code() == 404) {
|
|
|
|
|
new AlertDialog.Builder(this)
|
|
|
|
|
.setMessage(R.string.dialog_reply_not_found)
|
|
|
|
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
inReplyToId = null;
|
|
|
|
|
replyContentTextView.setVisibility(View.GONE);
|
|
|
|
|
replyTextView.setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
.show();
|
|
|
|
|
} else {
|
|
|
|
|
textEditor.setError(getString(R.string.error_generic));
|
|
|
|
|
}
|
2017-02-04 11:53:33 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void readyStatus(final Status.Visibility visibility, final boolean sensitive) {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
finishingUploadDialog = ProgressDialog.show(
|
2017-02-07 18:05:50 +11:00
|
|
|
|
this, getString(R.string.dialog_title_finishing_media_upload),
|
|
|
|
|
getString(R.string.dialog_message_uploading_media), true, true);
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@SuppressLint("StaticFieldLeak") final AsyncTask<Void, Void, Boolean> waitForMediaTask =
|
2017-01-20 15:59:21 +11:00
|
|
|
|
new AsyncTask<Void, Void, Boolean>() {
|
|
|
|
|
@Override
|
|
|
|
|
protected Boolean doInBackground(Void... params) {
|
|
|
|
|
try {
|
|
|
|
|
waitForMediaLatch.await();
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onPostExecute(Boolean successful) {
|
|
|
|
|
super.onPostExecute(successful);
|
2017-03-04 12:44:44 +11:00
|
|
|
|
finishingUploadDialog.dismiss();
|
|
|
|
|
finishingUploadDialog = null;
|
2017-01-20 15:59:21 +11:00
|
|
|
|
if (successful) {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
onReadySuccess(visibility, sensitive);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
} else {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
onReadyFailure(visibility, sensitive);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCancelled() {
|
|
|
|
|
removeAllMediaFromQueue();
|
2017-04-17 14:29:47 +10:00
|
|
|
|
setStateToNotReadying();
|
2017-01-20 15:59:21 +11:00
|
|
|
|
super.onCancelled();
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-03-04 12:44:44 +11:00
|
|
|
|
finishingUploadDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
2017-01-20 15:59:21 +11:00
|
|
|
|
@Override
|
|
|
|
|
public void onCancel(DialogInterface dialog) {
|
|
|
|
|
/* Generating an interrupt by passing true here is important because an interrupt
|
|
|
|
|
* exception is the only thing that will kick the latch out of its waiting loop
|
|
|
|
|
* early. */
|
|
|
|
|
waitForMediaTask.cancel(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
waitForMediaTask.execute();
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void onReadySuccess(Status.Visibility visibility, boolean sensitive) {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
/* Validate the status meets the character limit. This has to be delayed until after all
|
|
|
|
|
* uploads finish because their links are added when the upload succeeds and that affects
|
|
|
|
|
* whether the limit is met or not. */
|
|
|
|
|
String contentText = textEditor.getText().toString();
|
|
|
|
|
String spoilerText = "";
|
|
|
|
|
if (statusHideText) {
|
|
|
|
|
spoilerText = contentWarningEditor.getText().toString();
|
|
|
|
|
}
|
|
|
|
|
int characterCount = contentText.length() + spoilerText.length();
|
|
|
|
|
if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
|
|
|
|
|
sendStatus(contentText, visibility, sensitive, spoilerText);
|
|
|
|
|
} else if (characterCount <= 0) {
|
|
|
|
|
textEditor.setError(getString(R.string.error_empty));
|
|
|
|
|
setStateToNotReadying();
|
|
|
|
|
} else {
|
|
|
|
|
textEditor.setError(getString(R.string.error_compose_character_limit));
|
|
|
|
|
setStateToNotReadying();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private void onReadyFailure(final Status.Visibility visibility, final boolean sensitive) {
|
2017-01-20 15:59:21 +11:00
|
|
|
|
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry,
|
|
|
|
|
new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
readyStatus(visibility, sensitive);
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
|
|
|
|
});
|
2017-04-17 14:29:47 +10:00
|
|
|
|
setStateToNotReadying();
|
2017-01-20 15:59:21 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-28 12:40:58 +10:00
|
|
|
|
private void openPickDialog() {
|
|
|
|
|
final int CHOICE_TAKE = 0;
|
|
|
|
|
final int CHOICE_PICK = 1;
|
|
|
|
|
CharSequence[] choices = new CharSequence[2];
|
|
|
|
|
choices[CHOICE_TAKE] = getString(R.string.action_photo_take);
|
|
|
|
|
choices[CHOICE_PICK] = getString(R.string.action_photo_pick);
|
|
|
|
|
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
|
|
|
switch (which) {
|
|
|
|
|
case CHOICE_TAKE: {
|
|
|
|
|
initiateCameraApp();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case CHOICE_PICK: {
|
|
|
|
|
onMediaPick();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
|
|
|
.setItems(choices, listener)
|
|
|
|
|
.create();
|
|
|
|
|
dialog.show();
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private void onMediaPick() {
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
|
|
|
|
|
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
2017-05-16 18:43:32 +10:00
|
|
|
|
!= PackageManager.PERMISSION_GRANTED) {
|
2017-01-17 05:15:42 +11:00
|
|
|
|
ActivityCompat.requestPermissions(this,
|
2017-05-16 18:43:32 +10:00
|
|
|
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
2017-01-17 05:15:42 +11:00
|
|
|
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
|
|
|
|
|
} else {
|
|
|
|
|
initiateMediaPicking();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
|
2017-10-30 08:18:45 +11:00
|
|
|
|
@NonNull int[] grantResults) {
|
2017-01-17 05:15:42 +11:00
|
|
|
|
switch (requestCode) {
|
|
|
|
|
case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
|
|
|
|
|
if (grantResults.length > 0
|
|
|
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
initiateMediaPicking();
|
|
|
|
|
} else {
|
|
|
|
|
doErrorDialog(R.string.error_media_upload_permission, R.string.action_retry,
|
|
|
|
|
new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
onMediaPick();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 07:45:13 +10:00
|
|
|
|
@NonNull
|
2017-04-16 01:43:26 +10:00
|
|
|
|
private File createNewImageFile() throws IOException {
|
|
|
|
|
// Create an image file name
|
|
|
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
|
|
|
|
String imageFileName = "Tusky_" + timeStamp + "_";
|
|
|
|
|
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
2017-04-21 15:50:59 +10:00
|
|
|
|
return File.createTempFile(
|
2017-04-16 01:43:26 +10:00
|
|
|
|
imageFileName, /* prefix */
|
|
|
|
|
".jpg", /* suffix */
|
|
|
|
|
storageDir /* directory */
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initiateCameraApp() {
|
|
|
|
|
// We don't need to ask for permission in this case, because the used calls require
|
|
|
|
|
// android.permission.WRITE_EXTERNAL_STORAGE only on SDKs *older* than Kitkat, which was
|
|
|
|
|
// way before permission dialogues have been introduced.
|
|
|
|
|
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
|
|
|
if (intent.resolveActivity(getPackageManager()) != null) {
|
|
|
|
|
File photoFile = null;
|
|
|
|
|
try {
|
|
|
|
|
photoFile = createNewImageFile();
|
|
|
|
|
} catch (IOException ex) {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_opening);
|
|
|
|
|
}
|
|
|
|
|
// Continue only if the File was successfully created
|
|
|
|
|
if (photoFile != null) {
|
|
|
|
|
photoUploadUri = FileProvider.getUriForFile(this,
|
|
|
|
|
"com.keylesspalace.tusky.fileprovider",
|
|
|
|
|
photoFile);
|
|
|
|
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUploadUri);
|
|
|
|
|
startActivityForResult(intent, MEDIA_TAKE_PHOTO_RESULT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private void initiateMediaPicking() {
|
|
|
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
|
|
|
intent.setType("image/* video/*");
|
|
|
|
|
} else {
|
2017-05-16 18:43:32 +10:00
|
|
|
|
String[] mimeTypes = new String[]{"image/*", "video/*"};
|
2017-01-17 05:15:42 +11:00
|
|
|
|
intent.setType("*/*");
|
|
|
|
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
|
|
|
|
}
|
|
|
|
|
startActivityForResult(intent, MEDIA_PICK_RESULT);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-16 01:43:26 +10:00
|
|
|
|
private void enableMediaButtons() {
|
2017-07-28 12:40:58 +10:00
|
|
|
|
pickButton.setEnabled(true);
|
|
|
|
|
ThemeUtils.setDrawableTint(this, pickButton.getDrawable(),
|
2017-04-16 01:43:26 +10:00
|
|
|
|
R.attr.compose_media_button_tint);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-16 01:43:26 +10:00
|
|
|
|
private void disableMediaButtons() {
|
2017-07-28 12:40:58 +10:00
|
|
|
|
pickButton.setEnabled(false);
|
|
|
|
|
ThemeUtils.setDrawableTint(this, pickButton.getDrawable(),
|
2017-04-16 01:43:26 +10:00
|
|
|
|
R.attr.compose_media_button_disabled_tint);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-29 07:24:26 +11:00
|
|
|
|
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize, QueuedMedia.ReadyStage readyStage) {
|
2017-10-30 08:18:45 +11:00
|
|
|
|
final QueuedMedia item = new QueuedMedia(type, uri, new ProgressImageView(this), mediaSize);
|
2017-10-29 07:24:26 +11:00
|
|
|
|
item.readyStage = readyStage;
|
2017-02-17 05:52:55 +11:00
|
|
|
|
ImageView view = item.preview;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
Resources resources = getResources();
|
|
|
|
|
int side = resources.getDimensionPixelSize(R.dimen.compose_media_preview_side);
|
|
|
|
|
int margin = resources.getDimensionPixelSize(R.dimen.compose_media_preview_margin);
|
|
|
|
|
int marginBottom = resources.getDimensionPixelSize(
|
|
|
|
|
R.dimen.compose_media_preview_margin_bottom);
|
|
|
|
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(side, side);
|
2017-03-11 04:38:49 +11:00
|
|
|
|
layoutParams.setMargins(margin, 0, margin, marginBottom);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
view.setLayoutParams(layoutParams);
|
2017-03-15 11:34:27 +11:00
|
|
|
|
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
|
|
|
|
view.setImageBitmap(preview);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
view.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
removeMediaFromQueue(item);
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-07-19 12:32:13 +10:00
|
|
|
|
view.setContentDescription(getString(R.string.action_delete));
|
2017-01-17 05:15:42 +11:00
|
|
|
|
mediaPreviewBar.addView(view);
|
|
|
|
|
mediaQueued.add(item);
|
|
|
|
|
int queuedCount = mediaQueued.size();
|
|
|
|
|
if (queuedCount == 1) {
|
|
|
|
|
/* The media preview bar is actually not inset in the EditText, it just overlays it and
|
|
|
|
|
* is aligned to the bottom. But, so that text doesn't get hidden under it, extra
|
|
|
|
|
* padding is added at the bottom of the EditText. */
|
|
|
|
|
int totalHeight = side + margin + marginBottom;
|
2017-02-08 08:05:55 +11:00
|
|
|
|
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
|
|
|
|
|
textEditor.getPaddingRight(), totalHeight);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
// If there's one video in the queue it is full, so disable the button to queue more.
|
2017-02-17 05:52:55 +11:00
|
|
|
|
if (item.type == QueuedMedia.Type.VIDEO) {
|
2017-04-16 01:43:26 +10:00
|
|
|
|
disableMediaButtons();
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
} else if (queuedCount >= Status.MAX_MEDIA_ATTACHMENTS) {
|
|
|
|
|
// Limit the total media attachments, also.
|
2017-04-16 01:43:26 +10:00
|
|
|
|
disableMediaButtons();
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
if (queuedCount >= 1) {
|
2017-02-04 11:53:33 +11:00
|
|
|
|
showMarkSensitive(true);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-10-29 07:24:26 +11:00
|
|
|
|
if (item.readyStage != QueuedMedia.ReadyStage.UPLOADED) {
|
|
|
|
|
waitForMediaLatch.countUp();
|
|
|
|
|
if (mediaSize > STATUS_MEDIA_SIZE_LIMIT && type == QueuedMedia.Type.IMAGE) {
|
|
|
|
|
downsizeMedia(item);
|
|
|
|
|
} else {
|
|
|
|
|
uploadMedia(item);
|
|
|
|
|
}
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void removeMediaFromQueue(QueuedMedia item) {
|
2017-02-17 05:52:55 +11:00
|
|
|
|
mediaPreviewBar.removeView(item.preview);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
mediaQueued.remove(item);
|
|
|
|
|
if (mediaQueued.size() == 0) {
|
2017-02-04 11:53:33 +11:00
|
|
|
|
showMarkSensitive(false);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
/* If there are no image previews to show, the extra padding that was added to the
|
|
|
|
|
* EditText can be removed so there isn't unnecessary empty space. */
|
2017-02-08 08:05:55 +11:00
|
|
|
|
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
|
|
|
|
|
textEditor.getPaddingRight(), 0);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-07-12 11:49:46 +10:00
|
|
|
|
removeUrlFromEditable(textEditor.getEditableText(), item.uploadUrl);
|
2017-04-16 01:43:26 +10:00
|
|
|
|
enableMediaButtons();
|
2017-01-17 05:15:42 +11:00
|
|
|
|
cancelReadyingMedia(item);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 05:35:07 +11:00
|
|
|
|
private void removeAllMediaFromQueue() {
|
2017-05-16 18:43:32 +10:00
|
|
|
|
for (Iterator<QueuedMedia> it = mediaQueued.iterator(); it.hasNext(); ) {
|
2017-01-19 05:35:07 +11:00
|
|
|
|
QueuedMedia item = it.next();
|
|
|
|
|
it.remove();
|
|
|
|
|
removeMediaFromQueue(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-02 06:59:29 +11:00
|
|
|
|
private static Editable removeUrlFromEditable(Editable editable, @Nullable URLSpan urlSpan) {
|
2017-07-12 11:49:46 +10:00
|
|
|
|
if (urlSpan == null) {
|
2017-11-02 06:59:29 +11:00
|
|
|
|
return editable;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
SpannableStringBuilder builder = new SpannableStringBuilder(editable);
|
|
|
|
|
int start = builder.getSpanStart(urlSpan);
|
|
|
|
|
int end = builder.getSpanEnd(urlSpan);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
if (start != -1 && end != -1) {
|
2017-11-02 06:59:29 +11:00
|
|
|
|
builder.delete(start, end);
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
return builder;
|
2017-07-12 11:49:46 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-17 05:15:42 +11:00
|
|
|
|
private void downsizeMedia(final QueuedMedia item) {
|
2017-02-17 05:52:55 +11:00
|
|
|
|
item.readyStage = QueuedMedia.ReadyStage.DOWNSIZING;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
|
2017-04-01 08:53:35 +11:00
|
|
|
|
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, getContentResolver(),
|
|
|
|
|
new DownsizeImageTask.Listener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onSuccess(List<byte[]> contentList) {
|
|
|
|
|
item.content = contentList.get(0);
|
|
|
|
|
uploadMedia(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onFailure() {
|
|
|
|
|
onMediaDownsizeFailure(item);
|
|
|
|
|
}
|
2017-05-16 18:43:32 +10:00
|
|
|
|
}).execute(item.uri);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void onMediaDownsizeFailure(QueuedMedia item) {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_size);
|
|
|
|
|
removeMediaFromQueue(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void uploadMedia(final QueuedMedia item) {
|
2017-02-17 05:52:55 +11:00
|
|
|
|
item.readyStage = QueuedMedia.ReadyStage.UPLOADING;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
|
2017-10-30 08:18:45 +11:00
|
|
|
|
String mimeType = getContentResolver().getType(item.uri);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
MimeTypeMap map = MimeTypeMap.getSingleton();
|
|
|
|
|
String fileExtension = map.getExtensionFromMimeType(mimeType);
|
|
|
|
|
final String filename = String.format("%s_%s_%s.%s",
|
|
|
|
|
getString(R.string.app_name),
|
|
|
|
|
String.valueOf(new Date().getTime()),
|
2017-07-03 06:34:10 +10:00
|
|
|
|
StringUtils.randomAlphanumericString(10),
|
2017-01-17 05:15:42 +11:00
|
|
|
|
fileExtension);
|
|
|
|
|
|
2017-03-10 02:59:18 +11:00
|
|
|
|
byte[] content = item.content;
|
|
|
|
|
|
|
|
|
|
if (content == null) {
|
|
|
|
|
InputStream stream;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
stream = getContentResolver().openInputStream(item.uri);
|
|
|
|
|
} catch (FileNotFoundException e) {
|
2017-05-26 01:21:11 +10:00
|
|
|
|
Log.d(TAG, Log.getStackTraceString(e));
|
2017-03-10 02:59:18 +11:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 06:34:10 +10:00
|
|
|
|
content = MediaUtils.inputStreamGetBytes(stream);
|
2017-03-10 02:59:18 +11:00
|
|
|
|
IOUtils.closeQuietly(stream);
|
|
|
|
|
|
|
|
|
|
if (content == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-30 08:18:45 +11:00
|
|
|
|
if (mimeType == null) mimeType = "multipart/form-data";
|
|
|
|
|
|
|
|
|
|
item.preview.setProgress(0);
|
|
|
|
|
|
|
|
|
|
ProgressRequestBody fileBody = new ProgressRequestBody(content, MediaType.parse(mimeType),
|
|
|
|
|
false, // If request body logging is enabled, pass true
|
|
|
|
|
new ProgressRequestBody.UploadCallback() { // may reference activity longer than I would like to
|
|
|
|
|
int lastProgress = -1;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onProgressUpdate(final int percentage) {
|
|
|
|
|
if (percentage != lastProgress) {
|
|
|
|
|
runOnUiThread(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
item.preview.setProgress(percentage);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
lastProgress = percentage;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
MultipartBody.Part body = MultipartBody.Part.createFormData("file", filename, fileBody);
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
2017-06-23 04:01:25 +10:00
|
|
|
|
item.uploadRequest = mastodonApi.uploadMedia(body);
|
2017-03-10 02:59:18 +11:00
|
|
|
|
|
|
|
|
|
item.uploadRequest.enqueue(new Callback<Media>() {
|
2017-01-17 05:15:42 +11:00
|
|
|
|
@Override
|
2017-10-30 08:18:45 +11:00
|
|
|
|
public void onResponse(@NonNull Call<Media> call, @NonNull retrofit2.Response<Media> response) {
|
2017-03-14 11:49:12 +11:00
|
|
|
|
if (response.isSuccessful()) {
|
2017-05-04 10:27:59 +10:00
|
|
|
|
onUploadSuccess(item, response.body());
|
2017-03-14 11:49:12 +11:00
|
|
|
|
} else {
|
|
|
|
|
Log.d(TAG, "Upload request failed. " + response.message());
|
2017-03-15 11:34:27 +11:00
|
|
|
|
onUploadFailure(item, call.isCanceled());
|
2017-03-14 11:49:12 +11:00
|
|
|
|
}
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2017-10-30 08:18:45 +11:00
|
|
|
|
public void onFailure(@NonNull Call<Media> call, @NonNull Throwable t) {
|
2017-07-03 07:55:18 +10:00
|
|
|
|
Log.d(TAG, "Upload request failed. " + t.getMessage());
|
|
|
|
|
onUploadFailure(item, call.isCanceled());
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-03-10 02:59:18 +11:00
|
|
|
|
});
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-04 10:27:59 +10:00
|
|
|
|
private void onUploadSuccess(final QueuedMedia item, Media media) {
|
|
|
|
|
item.id = media.id;
|
2017-10-30 08:18:45 +11:00
|
|
|
|
item.preview.setProgress(-1);
|
2017-10-29 07:24:26 +11:00
|
|
|
|
item.readyStage = QueuedMedia.ReadyStage.UPLOADED;
|
2017-05-04 10:27:59 +10:00
|
|
|
|
|
|
|
|
|
/* Add the upload URL to the text field. Also, keep a reference to the span so if the user
|
|
|
|
|
* chooses to remove the media, the URL is also automatically removed. */
|
|
|
|
|
item.uploadUrl = new URLSpan(media.textUrl);
|
|
|
|
|
int end = 1 + media.textUrl.length();
|
|
|
|
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
|
|
|
builder.append(' ');
|
|
|
|
|
builder.append(media.textUrl);
|
2017-06-21 06:57:04 +10:00
|
|
|
|
builder.setSpan(item.uploadUrl, 1, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
int cursorStart = textEditor.getSelectionStart();
|
2017-05-04 10:27:59 +10:00
|
|
|
|
textEditor.append(builder);
|
2017-06-21 06:57:04 +10:00
|
|
|
|
if (cursorStart == textEditor.getText().length()) {
|
|
|
|
|
textEditor.setSelection(cursorStart);
|
|
|
|
|
}
|
2017-05-04 10:27:59 +10:00
|
|
|
|
|
|
|
|
|
waitForMediaLatch.countDown();
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-15 11:34:27 +11:00
|
|
|
|
private void onUploadFailure(QueuedMedia item, boolean isCanceled) {
|
2017-03-15 12:25:47 +11:00
|
|
|
|
if (!isCanceled) {
|
2017-03-15 11:34:27 +11:00
|
|
|
|
/* if the upload was voluntarily cancelled, such as if the user clicked on it to remove
|
|
|
|
|
* it from the queue, then don't display this error message. */
|
|
|
|
|
displayTransientError(R.string.error_media_upload_sending);
|
|
|
|
|
}
|
2017-11-08 01:01:20 +11:00
|
|
|
|
if (finishingUploadDialog != null && finishingUploadDialog.isShowing()) {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
finishingUploadDialog.cancel();
|
|
|
|
|
}
|
2017-07-03 07:55:18 +10:00
|
|
|
|
if (!isCanceled) {
|
|
|
|
|
// If it is canceled, it's already been removed, otherwise do it.
|
|
|
|
|
removeMediaFromQueue(item);
|
|
|
|
|
}
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void cancelReadyingMedia(QueuedMedia item) {
|
2017-02-17 05:52:55 +11:00
|
|
|
|
if (item.readyStage == QueuedMedia.ReadyStage.UPLOADING) {
|
2017-02-18 09:56:31 +11:00
|
|
|
|
item.uploadRequest.cancel();
|
|
|
|
|
}
|
|
|
|
|
if (item.id == null) {
|
|
|
|
|
/* The presence of an upload id is used to detect if it finished uploading or not, to
|
|
|
|
|
* prevent counting down twice on the same media item. */
|
|
|
|
|
waitForMediaLatch.countDown();
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data);
|
2017-07-03 07:45:13 +10:00
|
|
|
|
if (resultCode == RESULT_OK && requestCode == MEDIA_PICK_RESULT && data != null) {
|
2017-01-17 05:15:42 +11:00
|
|
|
|
Uri uri = data.getData();
|
2017-07-03 06:34:10 +10:00
|
|
|
|
long mediaSize = MediaUtils.getMediaSize(getContentResolver(), uri);
|
2017-03-04 12:44:44 +11:00
|
|
|
|
pickMedia(uri, mediaSize);
|
2017-07-03 07:45:13 +10:00
|
|
|
|
} else if (resultCode == RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) {
|
2017-07-03 06:34:10 +10:00
|
|
|
|
long mediaSize = MediaUtils.getMediaSize(getContentResolver(), photoUploadUri);
|
2017-04-21 15:50:59 +10:00
|
|
|
|
pickMedia(photoUploadUri, mediaSize);
|
2017-03-04 12:44:44 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-21 07:54:41 +10:00
|
|
|
|
|
2017-03-04 12:44:44 +11:00
|
|
|
|
private void pickMedia(Uri uri, long mediaSize) {
|
|
|
|
|
ContentResolver contentResolver = getContentResolver();
|
2017-07-03 06:34:10 +10:00
|
|
|
|
if (mediaSize == MediaUtils.MEDIA_SIZE_UNKNOWN) {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
displayTransientError(R.string.error_media_upload_opening);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
String mimeType = contentResolver.getType(uri);
|
|
|
|
|
if (mimeType != null) {
|
|
|
|
|
String topLevelType = mimeType.substring(0, mimeType.indexOf('/'));
|
|
|
|
|
switch (topLevelType) {
|
|
|
|
|
case "video": {
|
|
|
|
|
if (mediaSize > STATUS_MEDIA_SIZE_LIMIT) {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_size);
|
|
|
|
|
return;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-03-04 12:44:44 +11:00
|
|
|
|
if (mediaQueued.size() > 0
|
|
|
|
|
&& mediaQueued.get(0).type == QueuedMedia.Type.IMAGE) {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_image_or_video);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Bitmap bitmap = MediaUtils.getVideoThumbnail(this, uri, THUMBNAIL_SIZE);
|
2017-07-21 07:54:41 +10:00
|
|
|
|
if (bitmap != null) {
|
2017-10-29 07:24:26 +11:00
|
|
|
|
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize, null);
|
2017-07-21 07:54:41 +10:00
|
|
|
|
} else {
|
2017-07-19 14:28:36 +10:00
|
|
|
|
displayTransientError(R.string.error_media_upload_opening);
|
|
|
|
|
}
|
2017-03-04 12:44:44 +11:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "image": {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
Bitmap bitmap = MediaUtils.getImageThumbnail(contentResolver, uri, THUMBNAIL_SIZE);
|
2017-07-21 07:54:41 +10:00
|
|
|
|
if (bitmap != null) {
|
2017-10-29 07:24:26 +11:00
|
|
|
|
addMediaToQueue(QueuedMedia.Type.IMAGE, bitmap, uri, mediaSize, null);
|
2017-07-21 07:54:41 +10:00
|
|
|
|
} else {
|
2017-03-04 12:44:44 +11:00
|
|
|
|
displayTransientError(R.string.error_media_upload_opening);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-03-04 12:44:44 +11:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_type);
|
|
|
|
|
break;
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-04 12:44:44 +11:00
|
|
|
|
} else {
|
|
|
|
|
displayTransientError(R.string.error_media_upload_type);
|
2017-01-17 05:15:42 +11:00
|
|
|
|
}
|
2017-01-08 09:24:02 +11:00
|
|
|
|
}
|
2017-02-04 11:53:33 +11:00
|
|
|
|
|
2017-07-08 12:56:07 +10:00
|
|
|
|
private void showMarkSensitive(boolean show) {
|
2017-02-04 11:53:33 +11:00
|
|
|
|
showMarkSensitive = show;
|
2017-03-11 04:38:49 +11:00
|
|
|
|
|
2017-05-16 18:43:32 +10:00
|
|
|
|
if (!showMarkSensitive) {
|
2017-02-04 11:53:33 +11:00
|
|
|
|
statusMarkSensitive = false;
|
2017-06-21 04:16:03 +10:00
|
|
|
|
ThemeUtils.setDrawableTint(this, hideMediaToggle.getDrawable(),
|
|
|
|
|
R.attr.compose_hide_media_button_color);
|
2017-03-11 04:38:49 +11:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-16 18:43:32 +10:00
|
|
|
|
if (show) {
|
2017-06-21 04:16:03 +10:00
|
|
|
|
hideMediaToggle.setVisibility(View.VISIBLE);
|
2017-03-11 04:38:49 +11:00
|
|
|
|
} else {
|
2017-06-21 04:16:03 +10:00
|
|
|
|
hideMediaToggle.setVisibility(View.GONE);
|
2017-02-04 11:53:33 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-08 12:56:07 +10:00
|
|
|
|
private void showContentWarning(boolean show) {
|
2017-02-04 11:53:33 +11:00
|
|
|
|
statusHideText = show;
|
|
|
|
|
if (show) {
|
|
|
|
|
contentWarningBar.setVisibility(View.VISIBLE);
|
|
|
|
|
} else {
|
|
|
|
|
contentWarningBar.setVisibility(View.GONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-08 00:09:33 +11:00
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
|
switch (item.getItemId()) {
|
|
|
|
|
case android.R.id.home: {
|
|
|
|
|
onBackPressed();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return super.onOptionsItemSelected(item);
|
|
|
|
|
}
|
2017-05-16 18:43:32 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@Override
|
|
|
|
|
public List<Account> searchAccounts(String mention) {
|
2017-06-19 13:34:48 +10:00
|
|
|
|
ArrayList<Account> resultList = new ArrayList<>();
|
2017-06-19 12:10:50 +10:00
|
|
|
|
try {
|
2017-06-23 04:01:25 +10:00
|
|
|
|
List<Account> accountList = mastodonApi.searchAccounts(mention, false, 40)
|
2017-06-19 12:10:50 +10:00
|
|
|
|
.execute()
|
|
|
|
|
.body();
|
2017-06-19 13:34:48 +10:00
|
|
|
|
if (accountList != null) {
|
|
|
|
|
resultList.addAll(accountList);
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Log.e(TAG, String.format("Autocomplete search for %s failed.", mention));
|
|
|
|
|
}
|
|
|
|
|
return resultList;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private static final class QueuedMedia {
|
2017-05-16 18:43:32 +10:00
|
|
|
|
Type type;
|
2017-10-30 08:18:45 +11:00
|
|
|
|
ProgressImageView preview;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
Uri uri;
|
|
|
|
|
String id;
|
|
|
|
|
Call<Media> uploadRequest;
|
|
|
|
|
URLSpan uploadUrl;
|
|
|
|
|
ReadyStage readyStage;
|
|
|
|
|
byte[] content;
|
|
|
|
|
long mediaSize;
|
|
|
|
|
|
2017-10-30 08:18:45 +11:00
|
|
|
|
QueuedMedia(Type type, Uri uri, ProgressImageView preview, long mediaSize) {
|
2017-05-16 18:43:32 +10:00
|
|
|
|
this.type = type;
|
|
|
|
|
this.uri = uri;
|
|
|
|
|
this.preview = preview;
|
|
|
|
|
this.mediaSize = mediaSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Type {
|
|
|
|
|
IMAGE,
|
|
|
|
|
VIDEO
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum ReadyStage {
|
|
|
|
|
DOWNSIZING,
|
2017-10-29 07:24:26 +11:00
|
|
|
|
UPLOADING,
|
|
|
|
|
UPLOADED
|
2017-05-16 18:43:32 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This saves enough information to re-enqueue an attachment when restoring the activity.
|
|
|
|
|
*/
|
|
|
|
|
private static class SavedQueuedMedia implements Parcelable {
|
|
|
|
|
public static final Parcelable.Creator<SavedQueuedMedia> CREATOR
|
|
|
|
|
= new Parcelable.Creator<SavedQueuedMedia>() {
|
|
|
|
|
public SavedQueuedMedia createFromParcel(Parcel in) {
|
|
|
|
|
return new SavedQueuedMedia(in);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SavedQueuedMedia[] newArray(int size) {
|
|
|
|
|
return new SavedQueuedMedia[size];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
QueuedMedia.Type type;
|
|
|
|
|
Uri uri;
|
|
|
|
|
long mediaSize;
|
2017-10-29 07:24:26 +11:00
|
|
|
|
QueuedMedia.ReadyStage readyStage;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
|
2017-10-29 07:24:26 +11:00
|
|
|
|
SavedQueuedMedia(QueuedMedia.Type type, Uri uri, long mediaSize, QueuedMedia.ReadyStage readyStage) {
|
2017-05-16 18:43:32 +10:00
|
|
|
|
this.type = type;
|
|
|
|
|
this.uri = uri;
|
|
|
|
|
this.mediaSize = mediaSize;
|
2017-10-29 07:24:26 +11:00
|
|
|
|
this.readyStage = readyStage;
|
2017-05-16 18:43:32 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SavedQueuedMedia(Parcel parcel) {
|
|
|
|
|
type = (QueuedMedia.Type) parcel.readSerializable();
|
|
|
|
|
uri = parcel.readParcelable(Uri.class.getClassLoader());
|
|
|
|
|
mediaSize = parcel.readLong();
|
2017-10-29 07:24:26 +11:00
|
|
|
|
readyStage = QueuedMedia.ReadyStage.valueOf(parcel.readString());
|
2017-05-16 18:43:32 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int describeContents() {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void writeToParcel(Parcel dest, int flags) {
|
|
|
|
|
dest.writeSerializable(type);
|
|
|
|
|
dest.writeParcelable(uri, flags);
|
|
|
|
|
dest.writeLong(mediaSize);
|
2017-10-29 07:24:26 +11:00
|
|
|
|
dest.writeString(readyStage.name());
|
2017-05-16 18:43:32 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-19 12:10:50 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
/**
|
|
|
|
|
* Function to decide which visibility should be used for posting a status
|
|
|
|
|
*
|
|
|
|
|
* @return {@code PRIVATE} if account is locked, {@code PUBLIC} if both start and reply
|
|
|
|
|
* visibilities are unknown or minimal known visibility of two of them.
|
|
|
|
|
*/
|
|
|
|
|
private static Status.Visibility pickVisibility(final Status.Visibility startVisibility,
|
|
|
|
|
final Status.Visibility replyVisibility,
|
|
|
|
|
boolean isAccountLocked) {
|
|
|
|
|
// If the currently logged in account is locked, its posts should default to private.
|
|
|
|
|
// This should override even the reply settings.
|
|
|
|
|
if (isAccountLocked) {
|
|
|
|
|
return Status.Visibility.PRIVATE;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
if (startVisibility == Status.Visibility.UNKNOWN &&
|
|
|
|
|
replyVisibility == Status.Visibility.UNKNOWN) {
|
|
|
|
|
return Status.Visibility.PUBLIC;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
if (replyVisibility == Status.Visibility.UNKNOWN) {
|
|
|
|
|
return startVisibility;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
if (startVisibility == Status.Visibility.UNKNOWN) {
|
|
|
|
|
return replyVisibility;
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
2017-06-19 13:34:48 +10:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
if (startVisibility.getNum() > replyVisibility.getNum()) {
|
|
|
|
|
return startVisibility;
|
|
|
|
|
} else {
|
|
|
|
|
return replyVisibility;
|
2017-06-19 13:34:48 +10:00
|
|
|
|
}
|
2017-06-19 12:10:50 +10:00
|
|
|
|
}
|
2017-11-02 06:59:29 +11:00
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
@SuppressWarnings("WeakerAccess")
|
2017-11-02 06:59:29 +11:00
|
|
|
|
public static final class IntentBuilder {
|
|
|
|
|
@Nullable
|
|
|
|
|
private Integer savedTootUid;
|
|
|
|
|
@Nullable
|
|
|
|
|
private String savedTootText;
|
|
|
|
|
@Nullable
|
|
|
|
|
private String savedJsonUrls;
|
|
|
|
|
@Nullable
|
|
|
|
|
private Collection<String> mentionedUsernames;
|
|
|
|
|
@Nullable
|
|
|
|
|
private String inReplyToId;
|
|
|
|
|
@Nullable
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private Status.Visibility replyVisibility;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
@Nullable
|
|
|
|
|
private String contentWarning;
|
|
|
|
|
@Nullable
|
2017-11-17 05:18:11 +11:00
|
|
|
|
private String replyingStatusAuthor;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
@Nullable
|
|
|
|
|
private String replyingStatusContent;
|
|
|
|
|
|
|
|
|
|
public IntentBuilder savedTootUid(int uid) {
|
|
|
|
|
this.savedTootUid = uid;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder savedTootText(String savedTootText) {
|
|
|
|
|
this.savedTootText = savedTootText;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder savedJsonUrls(String jsonUrls) {
|
|
|
|
|
this.savedJsonUrls = jsonUrls;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder mentionedUsernames(Collection<String> mentionedUsernames) {
|
|
|
|
|
this.mentionedUsernames = mentionedUsernames;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder inReplyToId(String inReplyToId) {
|
|
|
|
|
this.inReplyToId = inReplyToId;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
public IntentBuilder replyVisibility(Status.Visibility replyVisibility) {
|
2017-11-02 06:59:29 +11:00
|
|
|
|
this.replyVisibility = replyVisibility;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder contentWarning(String contentWarning) {
|
|
|
|
|
this.contentWarning = contentWarning;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-17 05:18:11 +11:00
|
|
|
|
public IntentBuilder repyingStatusAuthor(String username) {
|
|
|
|
|
this.replyingStatusAuthor = username;
|
2017-11-02 06:59:29 +11:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IntentBuilder replyingStatusContent(String content) {
|
|
|
|
|
this.replyingStatusContent = content;
|
|
|
|
|
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 (savedJsonUrls != null) {
|
|
|
|
|
intent.putExtra(SAVED_JSON_URLS_EXTRA, savedJsonUrls);
|
|
|
|
|
}
|
|
|
|
|
if (mentionedUsernames != null) {
|
|
|
|
|
String[] usernames = mentionedUsernames.toArray(new String[0]);
|
|
|
|
|
intent.putExtra(MENTIONED_USERNAMES_EXTRA, usernames);
|
|
|
|
|
}
|
|
|
|
|
if (inReplyToId != null) {
|
|
|
|
|
intent.putExtra(IN_REPLY_TO_ID_EXTRA, inReplyToId);
|
|
|
|
|
}
|
|
|
|
|
if (replyVisibility != null) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility.getNum());
|
2017-11-02 06:59:29 +11:00
|
|
|
|
}
|
|
|
|
|
if (contentWarning != null) {
|
|
|
|
|
intent.putExtra(CONTENT_WARNING_EXTRA, contentWarning);
|
|
|
|
|
}
|
|
|
|
|
if (replyingStatusContent != null) {
|
|
|
|
|
intent.putExtra(REPLYING_STATUS_CONTENT_EXTRA, replyingStatusContent);
|
|
|
|
|
}
|
|
|
|
|
if (replyingStatusAuthor != null) {
|
2017-11-17 05:18:11 +11:00
|
|
|
|
intent.putExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA, replyingStatusAuthor);
|
2017-11-02 06:59:29 +11:00
|
|
|
|
}
|
|
|
|
|
return intent;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-08 09:24:02 +11:00
|
|
|
|
}
|