Reply improvements (#432)

* Refactor ComposeActivity intent. Fix bug with URLs

When user saved toot link was removed from the text field itself,
not only from the text to be saved.

* Show what you reply to

Closes #119
This commit is contained in:
Ivan Kupalov 2017-11-01 21:59:29 +02:00 committed by Konrad Pozniak
parent 2d390f6603
commit 62f4837135
10 changed files with 260 additions and 78 deletions

View file

@ -59,6 +59,7 @@ import com.squareup.picasso.Picasso;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import retrofit2.Call;
@ -650,8 +651,10 @@ public class AccountActivity extends BaseActivity implements ActionButtonActivit
// If the account isn't loaded yet, eat the input.
return false;
}
Intent intent = new Intent(this, ComposeActivity.class);
intent.putExtra("mentioned_usernames", new String[] { loadedAccount.username });
Intent intent = new ComposeActivity.IntentBuilder()
.mentionedUsernames(Collections.singleton(loadedAccount.username))
.build(this);
startActivity(intent);
startActivity(intent);
return true;
}

View file

@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.annotation.AttrRes;
import android.support.annotation.LayoutRes;
@ -110,6 +111,7 @@ import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@ -121,7 +123,7 @@ import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ComposeActivity extends BaseActivity implements ComposeOptionsFragment.Listener {
public final class ComposeActivity extends BaseActivity implements ComposeOptionsFragment.Listener {
private static final String TAG = "ComposeActivity"; // logging tag
private static final int STATUS_CHARACTER_LIMIT = 500;
private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608; // 8MiB
@ -130,8 +132,20 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
private static final int COMPOSE_SUCCESS = -1;
private static final int THUMBNAIL_SIZE = 128; // pixels
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";
private static TootDao tootDao = TuskyApplication.getDB().tootDao();
private TextView replyTextView;
private TextView replyContentTextView;
private EditTextTyped textEditor;
private LinearLayout mediaPreviewBar;
private View contentWarningBar;
@ -169,6 +183,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_compose);
replyTextView = findViewById(R.id.reply_tv);
replyContentTextView = findViewById(R.id.reply_content_tv);
textEditor = findViewById(R.id.compose_edit_field);
mediaPreviewBar = findViewById(R.id.compose_media_preview_bar);
contentWarningBar = findViewById(R.id.compose_content_warning_bar);
@ -275,8 +291,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
ArrayList<String> loadedDraftMediaUris = null;
inReplyToId = null;
if (intent != null) {
inReplyToId = intent.getStringExtra("in_reply_to_id");
String replyVisibility = intent.getStringExtra("reply_visibility");
inReplyToId = intent.getStringExtra(IN_REPLY_TO_ID_EXTRA);
String replyVisibility = intent.getStringExtra(REPLY_VISIBILITY_EXTRA);
if (replyVisibility != null && startingVisibility != null) {
// Lowest possible visibility setting in response
@ -291,15 +307,15 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
}
}
mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames");
mentionedUsernames = intent.getStringArrayExtra(MENTIONED_USERNAMES_EXTRA);
if (inReplyToId != null) {
startingHideText = !intent.getStringExtra("content_warning").equals("");
startingHideText = !intent.getStringExtra(CONTENT_WARNING_EXTRA).equals("");
if (startingHideText) {
startingContentWarning = intent.getStringExtra("content_warning");
startingContentWarning = intent.getStringExtra(CONTENT_WARNING_EXTRA);
}
} else {
String contentWarning = intent.getStringExtra("saved_toot_content_warning");
String contentWarning = intent.getStringExtra(CONTENT_WARNING_EXTRA);
if (contentWarning != null) {
startingHideText = !contentWarning.isEmpty();
if (startingHideText) {
@ -309,12 +325,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
}
// If come from SavedTootActivity
String savedTootText = intent.getStringExtra("saved_toot_text");
String savedTootText = intent.getStringExtra(SAVED_TOOT_TEXT_EXTRA);
if (!TextUtils.isEmpty(savedTootText)) {
textEditor.append(savedTootText);
}
String savedJsonUrls = intent.getStringExtra("saved_json_urls");
String savedJsonUrls = intent.getStringExtra(SAVED_JSON_URLS_EXTRA);
if (!TextUtils.isEmpty(savedJsonUrls)) {
// try to redo a list of media
loadedDraftMediaUris = new Gson().fromJson(savedJsonUrls,
@ -322,10 +338,30 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
}.getType());
}
int savedTootUid = intent.getIntExtra("saved_toot_uid", 0);
int savedTootUid = intent.getIntExtra(SAVED_TOOT_UID_EXTRA, 0);
if (savedTootUid != 0) {
this.savedTootUid = savedTootUid;
}
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));
}
}
/* If the currently logged in account is locked, its posts should default to private. This
@ -460,6 +496,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
}
}
}
}
@Override
@ -533,19 +571,20 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
if (statusHideText) {
contentWarning = contentWarningEditor.getText().toString();
}
Editable textToSave = textEditor.getEditableText();
/* 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) {
removeUrlFromEditable(textEditor.getEditableText(), item.uploadUrl);
textToSave = removeUrlFromEditable(textToSave, item.uploadUrl);
}
}
boolean b = saveTheToot(textEditor.getText().toString(), contentWarning);
if (b) {
boolean didSaveSuccessfully = saveTheToot(textToSave.toString(), contentWarning);
if (didSaveSuccessfully) {
Toast.makeText(ComposeActivity.this, R.string.action_save_one_toot, Toast.LENGTH_SHORT)
.show();
}
return b;
return didSaveSuccessfully;
}
private static boolean copyToFile(ContentResolver contentResolver, Uri uri, File file) {
@ -1228,15 +1267,17 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
}
}
private static void removeUrlFromEditable(Editable editable, @Nullable URLSpan urlSpan) {
private static Editable removeUrlFromEditable(Editable editable, @Nullable URLSpan urlSpan) {
if (urlSpan == null) {
return;
return editable;
}
int start = editable.getSpanStart(urlSpan);
int end = editable.getSpanEnd(urlSpan);
SpannableStringBuilder builder = new SpannableStringBuilder(editable);
int start = builder.getSpanStart(urlSpan);
int end = builder.getSpanEnd(urlSpan);
if (start != -1 && end != -1) {
editable.delete(start, end);
builder.delete(start, end);
}
return builder;
}
private void downsizeMedia(final QueuedMedia item) {
@ -1710,4 +1751,105 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFragm
return view;
}
}
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
private String replyVisibility;
@Nullable
private String contentWarning;
@Nullable
private Account replyingStatusAuthor;
@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;
}
public IntentBuilder replyVisibility(String replyVisibility) {
this.replyVisibility = replyVisibility;
return this;
}
public IntentBuilder contentWarning(String contentWarning) {
this.contentWarning = contentWarning;
return this;
}
public IntentBuilder repyingStatusAuthor(Account author) {
this.replyingStatusAuthor = author;
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) {
intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility);
}
if (contentWarning != null) {
intent.putExtra(CONTENT_WARNING_EXTRA, contentWarning);
}
if (replyingStatusContent != null) {
intent.putExtra(REPLYING_STATUS_CONTENT_EXTRA, replyingStatusContent);
}
if (replyingStatusAuthor != null) {
intent.putExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA,
replyingStatusAuthor.localUsername);
}
return intent;
}
}
}

View file

@ -150,11 +150,12 @@ public class SavedTootActivity extends BaseActivity implements SavedTootAdapter.
@Override
public void click(int position, TootEntity item) {
Intent intent = new Intent(this, ComposeActivity.class);
intent.putExtra("saved_toot_uid", item.getUid());
intent.putExtra("saved_toot_text", item.getText());
intent.putExtra("saved_toot_content_warning", item.getContentWarning());
intent.putExtra("saved_json_urls", item.getUrls());
Intent intent = new ComposeActivity.IntentBuilder()
.savedTootUid(item.getUid())
.savedTootText(item.getText())
.contentWarning(item.getContentWarning())
.savedJsonUrls(item.getUrls())
.build(this);
startActivity(intent);
}
}

View file

@ -26,9 +26,7 @@ import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
@ -103,11 +101,14 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov
mentionedUsernames.add(mention.username);
}
mentionedUsernames.remove(loggedInUsername);
Intent intent = new Intent(getContext(), ComposeActivity.class);
intent.putExtra("in_reply_to_id", inReplyToId);
intent.putExtra("reply_visibility", replyVisibility);
intent.putExtra("content_warning", contentWarning);
intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0]));
Intent intent = new ComposeActivity.IntentBuilder()
.inReplyToId(inReplyToId)
.replyVisibility(replyVisibility)
.contentWarning(contentWarning)
.mentionedUsernames(mentionedUsernames)
.repyingStatusAuthor(actionableStatus.account)
.replyingStatusContent(actionableStatus.content.toString())
.build(getContext());
startActivityForResult(intent, COMPOSE_RESULT);
}

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_compose"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -18,12 +18,38 @@
android:layout_marginBottom="8dp"
android:background="@android:color/transparent" />
<TextView
android:id="@+id/reply_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textStyle="bold"
android:visibility="gone"
tools:text="Reply to @username"
tools:visibility="visible" />
<TextView
android:id="@+id/reply_content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="?attr/compose_reply_content_background"
android:paddingBottom="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="4dp"
android:visibility="gone"
tools:text="Post content which may be preeettyy long, so please, make sure there's enough room for everything, okay? Not kidding. I wish Eugen answered me more often, sigh."
tools:visibility="visible" />
<LinearLayout
android:id="@+id/compose_content_warning_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="8dp">
android:layout_marginBottom="8dp"
android:orientation="vertical">
<EditText
android:id="@+id/field_content_warning"
@ -31,16 +57,16 @@
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:ems="10"
android:hint="@string/hint_content_warning"
android:inputType="text|textCapSentences"
android:maxLines="1"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:hint="@string/hint_content_warning"
android:inputType="text|textCapSentences" />
android:paddingRight="16dp" />
<View
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
</LinearLayout>
@ -54,16 +80,16 @@
android:paddingRight="16dp">
<com.keylesspalace.tusky.view.EditTextTyped
android:id="@+id/compose_edit_field"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/compose_edit_field"
android:background="@android:color/transparent"
android:completionThreshold="2"
android:dropDownWidth="wrap_content"
android:ems="10"
android:gravity="start|top"
android:hint="@string/hint_compose"
android:inputType="text|textMultiLine|textCapSentences"
android:dropDownWidth="wrap_content"
android:completionThreshold="2" />
android:inputType="text|textMultiLine|textCapSentences" />
<HorizontalScrollView
android:layout_width="match_parent"
@ -96,10 +122,10 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingBottom="8dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="16dp"
android:paddingLeft="8dp"
android:paddingRight="16dp"
android:paddingStart="8dp"
android:paddingTop="4dp">
<ImageButton
@ -107,57 +133,57 @@
style="?attr/image_button_style"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:contentDescription="@string/action_photo_pick"
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_attach_file_24dp"
android:contentDescription="@string/action_photo_pick" />
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
app:srcCompat="@drawable/ic_attach_file_24dp" />
<ImageButton
android:id="@+id/action_toggle_visibility"
style="?attr/image_button_style"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:contentDescription="@string/action_compose_options"
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_public_24dp"
android:contentDescription="@string/action_compose_options" />
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
app:srcCompat="@drawable/ic_public_24dp" />
<ImageButton
android:id="@+id/compose_save_draft"
style="?attr/image_button_style"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:contentDescription="@string/action_save"
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_save_24dp"
android:contentDescription="@string/action_save" />
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
app:srcCompat="@drawable/ic_save_24dp" />
<ImageButton
android:id="@+id/action_hide_media"
style="?attr/image_button_style"
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_hide_media_24dp"
android:contentDescription="@string/action_hide_media"
android:visibility="gone" />
android:paddingBottom="4dp"
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_hide_media_24dp" />
<android.support.v4.widget.Space
android:layout_width="0dp"
@ -172,11 +198,11 @@
<Button
android:id="@+id/floating_btn"
android:background="@drawable/compose_button_colors"
android:layout_width="80dp"
android:layout_height="35dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:background="@drawable/compose_button_colors"
android:text="@string/action_send"
android:textColor="@android:color/white" />

View file

@ -218,6 +218,7 @@
<string name="follows_you">Подписан(а) на вас</string>
<string name="pref_title_alway_show_sensitive_media">Всегда показывать NSFW-контент</string>
<string name="replying_to">Ответ @%s</string>
</resources>

View file

@ -37,6 +37,7 @@
<attr name="compose_hide_media_button_color" format="reference|color" />
<attr name="compose_hide_media_button_selected_color" format="reference|color" />
<attr name="compose_image_button_tint" format="reference|color" />
<attr name="compose_reply_content_background" format="reference|color" />
<attr name="report_status_background_color" format="reference|color" />
<attr name="report_status_divider_drawable" format="reference" />
<attr name="card_background" format="reference|color" />

View file

@ -38,6 +38,7 @@
<color name="report_status_background_dark">#000000</color>
<color name="report_status_divider_dark">#2F2F2F</color>
<color name="custom_tab_toolbar_dark">#313543</color>
<color name="compose_reply_content_background_dark">#373c4b</color>
<!--Light Theme Colors-->
<color name="color_primary_light">#dfdfdf</color>
<color name="color_primary_dark_light">#8f8f8f</color>
@ -72,5 +73,6 @@
<color name="report_status_background_light">#EFEFEF</color>
<color name="report_status_divider_light">#9F9F9F</color>
<color name="custom_tab_toolbar_light">#ffffff</color>
<color name="compose_reply_content_background_light">#e0e1e6</color>
</resources>

View file

@ -232,5 +232,6 @@
<string name="follows_you">Follows you</string>
<string name="pref_title_alway_show_sensitive_media">Always show all nsfw content</string>
<string name="replying_to">Replying to @%s</string>
</resources>

View file

@ -68,6 +68,8 @@
<item name="compose_hide_media_button_color">@color/image_button_dark</item>
<item name="compose_hide_media_button_selected_color">@color/color_accent_dark</item>
<item name="compose_image_button_tint">@color/image_button_dark</item>
<item name="compose_reply_content_background">@color/compose_reply_content_background_dark</item>
<item name="report_status_background_color">@color/color_background_dark</item>
<item name="report_status_divider_drawable">@drawable/status_divider_dark</item>
@ -148,6 +150,8 @@
<item name="compose_hide_media_button_color">@color/image_button_light</item>
<item name="compose_hide_media_button_selected_color">@color/color_accent_light</item>
<item name="compose_image_button_tint">@color/image_button_light</item>
<item name="compose_reply_content_background">@color/compose_reply_content_background_light</item>
<item name="report_status_background_color">@color/report_status_background_light</item>
<item name="report_status_divider_drawable">@drawable/report_status_divider_light</item>