From 5b581fe7f337028d6b5449871954bc9e0e437aad Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Wed, 1 Nov 2017 21:59:29 +0200 Subject: [PATCH] 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 --- .../keylesspalace/tusky/AccountActivity.java | 7 +- .../keylesspalace/tusky/ComposeActivity.java | 180 ++++++++++++++++-- .../tusky/SavedTootActivity.java | 11 +- .../tusky/fragment/SFragment.java | 15 +- app/src/main/res/layout/activity_compose.xml | 108 +++++++---- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 4 + 10 files changed, 256 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index 268fd28f..be0257ea 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -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; } diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 26411c72..24085a70 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -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 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 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 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; + } + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 6826660b..9aa567c9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -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); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 2c86c4fd..77918c7f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -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); } diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index 00b87505..232dfa4f 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -1,7 +1,7 @@ - @@ -18,12 +18,38 @@ android:layout_marginBottom="8dp" android:background="@android:color/transparent" /> + + + + + android:layout_marginBottom="8dp" + android:orientation="vertical"> + android:paddingRight="16dp" /> @@ -54,16 +80,16 @@ android:paddingRight="16dp"> + android:inputType="text|textMultiLine|textCapSentences" /> + android:paddingStart="4dp" + android:paddingTop="4dp" + app:srcCompat="@drawable/ic_attach_file_24dp" /> + android:paddingStart="4dp" + android:paddingTop="4dp" + app:srcCompat="@drawable/ic_public_24dp" /> + android:paddingStart="4dp" + android:paddingTop="4dp" + app:srcCompat="@drawable/ic_save_24dp" /> + android:paddingStart="4dp" + android:paddingTop="4dp" + android:visibility="gone" + app:srcCompat="@drawable/ic_hide_media_24dp" /> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3657b124..5b5b70cf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -218,6 +218,7 @@ Подписан(а) на вас Всегда показывать NSFW-контент + Ответ @%s diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 4d2dd849..690ad794 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -37,6 +37,7 @@ + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index dfbd8dbc..5a18fb9b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -38,6 +38,7 @@ #000000 #2F2F2F #313543 + #373c4b #dfdfdf #8f8f8f @@ -72,5 +73,6 @@ #EFEFEF #9F9F9F #ffffff + #e0e1e6 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e0e5790..731340c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -232,5 +232,6 @@ Follows you Always show all nsfw content + Replying to @%s diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 64e5480f..930c82c1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -68,6 +68,8 @@ @color/image_button_dark @color/color_accent_dark @color/image_button_dark + @color/compose_reply_content_background_dark + @color/color_background_dark @drawable/status_divider_dark @@ -148,6 +150,8 @@ @color/image_button_light @color/color_accent_light @color/image_button_light + @color/compose_reply_content_background_light + @color/report_status_background_light @drawable/report_status_divider_light