[quote] Implement quote feature
This commit is contained in:
parent
27a9fc1438
commit
573be935a7
|
@ -349,7 +349,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
accountDisplayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, accountDisplayNameTextView)
|
||||
|
||||
val emojifiedNote = CustomEmojiHelper.emojifyText(account.note, account.emojis, accountNoteTextView)
|
||||
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this)
|
||||
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this, false)
|
||||
|
||||
accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
|
|
|
@ -17,10 +17,10 @@ package com.keylesspalace.tusky
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.keylesspalace.tusky.entity.SearchResults
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
|
@ -173,6 +173,7 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
// https://pleroma.foo.bar/users/43456787654678
|
||||
// https://pleroma.foo.bar/notice/43456787654678
|
||||
// https://pleroma.foo.bar/objects/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
||||
// https://mastodon.foo.bar/users/User/statuses/000000000000000000
|
||||
fun looksLikeMastodonUrl(urlString: String): Boolean {
|
||||
val uri: URI
|
||||
try {
|
||||
|
@ -192,5 +193,6 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
|||
path.matches("^/users/[^/]+$".toRegex()) ||
|
||||
path.matches("^/@[^/]+/\\d+$".toRegex()) ||
|
||||
path.matches("^/notice/\\d+$".toRegex()) ||
|
||||
path.matches("^/objects/[-a-f0-9]+$".toRegex())
|
||||
path.matches("^/objects/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/users/[^/]+/statuses/[0-9]+$".toRegex())
|
||||
}
|
||||
|
|
|
@ -187,6 +187,8 @@ public final class ComposeActivity
|
|||
private static final String SAVED_JSON_DESCRIPTIONS_EXTRA = "saved_json_descriptions";
|
||||
private static final String TOOT_VISIBILITY_EXTRA = "toot_visibility";
|
||||
private static final String IN_REPLY_TO_ID_EXTRA = "in_reply_to_id";
|
||||
private static final String QUOTE_ID_EXTRA = "quote_id";
|
||||
private static final String QUOTE_URL_EXTRA = "quote_url";
|
||||
private static final String REPLY_VISIBILITY_EXTRA = "reply_visibility";
|
||||
private static final String CONTENT_WARNING_EXTRA = "content_warning";
|
||||
private static final String MENTIONED_USERNAMES_EXTRA = "mentioned_usernames";
|
||||
|
@ -201,6 +203,7 @@ public final class ComposeActivity
|
|||
private static final int MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420;
|
||||
|
||||
private static final String[] CAN_USE_UNLEAKABLE = {"itabashi.0j0.jp", "odakyu.app"};
|
||||
private static final String[] CAN_USE_QUOTE_ID = {"odakyu.app", "biwakodon.com", "dtp-mstdn.jp", "nitiasa.com"};
|
||||
|
||||
@Inject
|
||||
public MastodonApi mastodonApi;
|
||||
|
@ -235,6 +238,8 @@ public final class ComposeActivity
|
|||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||
private ProgressDialog finishingUploadDialog;
|
||||
private String inReplyToId;
|
||||
private String quoteId;
|
||||
private String quoteUrl;
|
||||
private List<QueuedMedia> mediaQueued = new ArrayList<>();
|
||||
private CountUpDownLatch waitForMediaLatch;
|
||||
private NewPoll poll;
|
||||
|
@ -270,6 +275,7 @@ public final class ComposeActivity
|
|||
|
||||
replyTextView = findViewById(R.id.composeReplyView);
|
||||
replyContentTextView = findViewById(R.id.composeReplyContentView);
|
||||
TextView quoteTextView = findViewById(R.id.composeQuoteView);
|
||||
textEditor = findViewById(R.id.composeEditField);
|
||||
mediaPreviewBar = findViewById(R.id.compose_media_preview_bar);
|
||||
contentWarningBar = findViewById(R.id.composeContentWarningBar);
|
||||
|
@ -441,6 +447,8 @@ public final class ComposeActivity
|
|||
ArrayList<String> loadedDraftMediaDescriptions = null;
|
||||
ArrayList<Attachment> mediaAttachments = null;
|
||||
inReplyToId = null;
|
||||
quoteId = null;
|
||||
quoteUrl = null;
|
||||
if (intent != null) {
|
||||
|
||||
if (startingVisibility == Status.Visibility.UNKNOWN) {
|
||||
|
@ -453,6 +461,14 @@ public final class ComposeActivity
|
|||
|
||||
inReplyToId = intent.getStringExtra(IN_REPLY_TO_ID_EXTRA);
|
||||
|
||||
quoteId = intent.getStringExtra(QUOTE_ID_EXTRA);
|
||||
|
||||
if (intent.hasExtra(QUOTE_URL_EXTRA)) {
|
||||
quoteTextView.setVisibility(View.VISIBLE);
|
||||
quoteUrl = intent.getStringExtra(QUOTE_URL_EXTRA);
|
||||
quoteTextView.setText(String.format(getString(R.string.quote_to), quoteUrl));
|
||||
}
|
||||
|
||||
mentionedUsernames = intent.getStringArrayExtra(MENTIONED_USERNAMES_EXTRA);
|
||||
|
||||
String contentWarning = intent.getStringExtra(CONTENT_WARNING_EXTRA);
|
||||
|
@ -1086,7 +1102,7 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
private void sendStatus(String content, Status.Visibility visibility, boolean sensitive,
|
||||
String spoilerText) {
|
||||
String spoilerText, @Nullable String quoteId, @Nullable String quoteUrl) {
|
||||
ArrayList<String> mediaIds = new ArrayList<>();
|
||||
ArrayList<Uri> mediaUris = new ArrayList<>();
|
||||
ArrayList<String> mediaDescriptions = new ArrayList<>();
|
||||
|
@ -1096,12 +1112,22 @@ public final class ComposeActivity
|
|||
mediaDescriptions.add(item.description);
|
||||
}
|
||||
|
||||
Intent sendIntent = SendTootService.sendTootIntent(this, content, spoilerText,
|
||||
Intent sendIntent;
|
||||
AccountEntity activeAccount = accountManager.getActiveAccount();
|
||||
|
||||
if (activeAccount != null
|
||||
&& !Arrays.asList(CAN_USE_QUOTE_ID).contains(activeAccount.getDomain())
|
||||
&& quoteUrl != null) {
|
||||
content += "\n~~~~~~~~~~\n[" + quoteUrl + "]";
|
||||
quoteId = null;
|
||||
}
|
||||
|
||||
sendIntent = SendTootService.sendTootIntent(this, content, spoilerText,
|
||||
visibility, !mediaUris.isEmpty() && sensitive, mediaIds, mediaUris, mediaDescriptions, inReplyToId, poll,
|
||||
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
|
||||
getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA),
|
||||
getIntent().getStringExtra(SAVED_JSON_URLS_EXTRA),
|
||||
accountManager.getActiveAccount(), savedTootUid);
|
||||
quoteId, accountManager.getActiveAccount(), savedTootUid);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(sendIntent);
|
||||
|
@ -1169,7 +1195,7 @@ public final class ComposeActivity
|
|||
textEditor.setError(getString(R.string.error_empty));
|
||||
enableButtons();
|
||||
} else if (characterCount <= maximumTootCharacters) {
|
||||
sendStatus(contentText, visibility, sensitive, spoilerText);
|
||||
sendStatus(contentText, visibility, sensitive, spoilerText, quoteId, quoteUrl);
|
||||
|
||||
} else {
|
||||
textEditor.setError(getString(R.string.error_compose_character_limit));
|
||||
|
@ -2074,6 +2100,10 @@ public final class ComposeActivity
|
|||
@Nullable
|
||||
private String inReplyToId;
|
||||
@Nullable
|
||||
private String quoteId;
|
||||
@Nullable
|
||||
private String quoteUrl;
|
||||
@Nullable
|
||||
private Status.Visibility replyVisibility;
|
||||
@Nullable
|
||||
private Status.Visibility visibility;
|
||||
|
@ -2125,6 +2155,16 @@ public final class ComposeActivity
|
|||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder quoteId(String quoteId) {
|
||||
this.quoteId = quoteId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder quoteUrl(String quoteUrl) {
|
||||
this.quoteUrl = quoteUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder replyVisibility(Status.Visibility replyVisibility) {
|
||||
this.replyVisibility = replyVisibility;
|
||||
return this;
|
||||
|
@ -2182,6 +2222,12 @@ public final class ComposeActivity
|
|||
if (inReplyToId != null) {
|
||||
intent.putExtra(IN_REPLY_TO_ID_EXTRA, inReplyToId);
|
||||
}
|
||||
if (quoteId != null) {
|
||||
intent.putExtra(QUOTE_ID_EXTRA, quoteId);
|
||||
}
|
||||
if (quoteUrl != null) {
|
||||
intent.putExtra(QUOTE_URL_EXTRA, quoteUrl);
|
||||
}
|
||||
if (replyVisibility != null) {
|
||||
intent.putExtra(REPLY_VISIBILITY_EXTRA, replyVisibility.getNum());
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
package com.keylesspalace.tusky.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Field
|
||||
|
@ -47,7 +47,7 @@ class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView
|
|||
viewHolder.nameTextView.text = emojifiedName
|
||||
|
||||
val emojifiedValue = CustomEmojiHelper.emojifyText(field.value, emojis, viewHolder.valueTextView)
|
||||
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener)
|
||||
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener, false)
|
||||
|
||||
if(field.verifiedAt != null) {
|
||||
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||
|
|
|
@ -33,10 +33,18 @@ import android.widget.ImageView;
|
|||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
||||
|
@ -48,17 +56,13 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
|||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
import com.mikepenz.iconics.utils.Utils;
|
||||
|
||||
import net.accelf.yuito.QuoteInlineHelper;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||
|
||||
public interface AdapterDataSource<T> {
|
||||
|
@ -352,6 +356,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
private final TextView contentWarningDescriptionTextView;
|
||||
private final ToggleButton contentWarningButton;
|
||||
private final ToggleButton contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
|
||||
private ConstraintLayout quoteContainer;
|
||||
|
||||
private String accountId;
|
||||
private String notificationId;
|
||||
|
@ -377,6 +382,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
|
||||
contentCollapseButton = itemView.findViewById(R.id.button_toggle_notification_content);
|
||||
|
||||
quoteContainer = itemView.findViewById(R.id.status_quote_inline_container);
|
||||
|
||||
int darkerFilter = Color.rgb(123, 123, 123);
|
||||
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
||||
notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
||||
|
@ -520,6 +527,15 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
notificationAvatar, notificationAvatarRadius, animateAvatar);
|
||||
}
|
||||
|
||||
private void setQuoteContainer(Status status, final LinkListener listener) {
|
||||
if (status != null) {
|
||||
quoteContainer.setVisibility(View.VISIBLE);
|
||||
new QuoteInlineHelper(status, quoteContainer, listener).setupQuoteContainer();
|
||||
} else {
|
||||
quoteContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
|
@ -570,11 +586,14 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
}
|
||||
|
||||
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, statusContent);
|
||||
LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener);
|
||||
LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener,
|
||||
notificationViewData.getStatusViewData().getQuote() != null);
|
||||
|
||||
Spanned emojifiedContentWarning =
|
||||
CustomEmojiHelper.emojifyString(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView);
|
||||
contentWarningDescriptionTextView.setText(emojifiedContentWarning);
|
||||
|
||||
setQuoteContainer(statusViewData.getQuote(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
@ -16,6 +18,7 @@ import android.widget.ToggleButton;
|
|||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -41,6 +44,8 @@ import com.keylesspalace.tusky.viewdata.PollViewDataKt;
|
|||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
import com.mikepenz.iconics.utils.Utils;
|
||||
|
||||
import net.accelf.yuito.QuoteInlineHelper;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
@ -62,6 +67,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
private ImageButton replyButton;
|
||||
private SparkButton reblogButton;
|
||||
private SparkButton favouriteButton;
|
||||
private ImageButton quoteButton;
|
||||
private ImageButton moreButton;
|
||||
protected MediaPreviewImageView[] mediaPreviews;
|
||||
private ImageView[] mediaOverlays;
|
||||
|
@ -76,6 +82,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
public TextView content;
|
||||
public TextView contentWarningDescription;
|
||||
|
||||
private ConstraintLayout quoteContainer;
|
||||
|
||||
private RecyclerView pollOptions;
|
||||
private TextView pollDescription;
|
||||
private Button pollButton;
|
||||
|
@ -105,6 +113,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
replyButton = itemView.findViewById(R.id.status_reply);
|
||||
reblogButton = itemView.findViewById(R.id.status_inset);
|
||||
favouriteButton = itemView.findViewById(R.id.status_favourite);
|
||||
quoteButton = itemView.findViewById(R.id.status_quote);
|
||||
moreButton = itemView.findViewById(R.id.status_more);
|
||||
|
||||
mediaPreviews = new MediaPreviewImageView[]{
|
||||
|
@ -131,6 +140,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
|
||||
avatarInset = itemView.findViewById(R.id.status_avatar_inset);
|
||||
|
||||
quoteContainer = itemView.findViewById(R.id.status_quote_inline_container);
|
||||
|
||||
pollOptions = itemView.findViewById(R.id.status_poll_options);
|
||||
pollDescription = itemView.findViewById(R.id.status_poll_description);
|
||||
pollButton = itemView.findViewById(R.id.status_poll_button);
|
||||
|
@ -174,11 +185,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
@Nullable String spoilerText,
|
||||
@Nullable Status.Mention[] mentions,
|
||||
@NonNull List<Emoji> emojis,
|
||||
final StatusActionListener listener) {
|
||||
final StatusActionListener listener,
|
||||
boolean removeQuote) {
|
||||
if (TextUtils.isEmpty(spoilerText)) {
|
||||
contentWarningDescription.setVisibility(View.GONE);
|
||||
contentWarningButton.setVisibility(View.GONE);
|
||||
this.setTextVisible(true, content, mentions, emojis, listener);
|
||||
this.setTextVisible(true, content, mentions, emojis, listener, removeQuote);
|
||||
} else {
|
||||
CharSequence emojiSpoiler = CustomEmojiHelper.emojifyString(spoilerText, emojis, contentWarningDescription);
|
||||
contentWarningDescription.setText(emojiSpoiler);
|
||||
|
@ -189,9 +201,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onExpandedChange(isChecked, getAdapterPosition());
|
||||
}
|
||||
this.setTextVisible(isChecked, content, mentions, emojis, listener);
|
||||
this.setTextVisible(isChecked, content, mentions, emojis, listener, removeQuote);
|
||||
});
|
||||
this.setTextVisible(expanded, content, mentions, emojis, listener);
|
||||
this.setTextVisible(expanded, content, mentions, emojis, listener, removeQuote);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,10 +211,11 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
Spanned content,
|
||||
Status.Mention[] mentions,
|
||||
List<Emoji> emojis,
|
||||
final StatusActionListener listener) {
|
||||
final StatusActionListener listener,
|
||||
boolean removeQuote) {
|
||||
if (expanded) {
|
||||
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, this.content);
|
||||
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
|
||||
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener, removeQuote);
|
||||
} else {
|
||||
LinkHelper.setClickableMentions(this.content, mentions, listener);
|
||||
}
|
||||
|
@ -334,10 +347,37 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
private void setQuoteEnabled(boolean enabled, Status.Visibility visibility) {
|
||||
quoteButton.setEnabled(enabled && visibility != Status.Visibility.PRIVATE && visibility != Status.Visibility.UNLEAKABLE);
|
||||
|
||||
if (enabled && visibility != Status.Visibility.PRIVATE && visibility != Status.Visibility.UNLEAKABLE) {
|
||||
int activeId;
|
||||
activeId = ThemeUtils.getDrawableId(quoteButton.getContext(),
|
||||
R.attr.status_quote_drawable, R.drawable.ic_quote_24dp);
|
||||
quoteButton.setImageResource(activeId);
|
||||
} else {
|
||||
Resources res = quoteButton.getContext().getResources();
|
||||
Drawable disableIcon = res.getDrawable(R.drawable.ic_quote_disabled_24dp);
|
||||
if (disableIcon != null) {
|
||||
disableIcon.setColorFilter(res.getColor(R.color.status_reblog_button_disabled_dark), PorterDuff.Mode.DST_IN);
|
||||
}
|
||||
quoteButton.setImageDrawable(disableIcon);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setFavourited(boolean favourited) {
|
||||
favouriteButton.setChecked(favourited);
|
||||
}
|
||||
|
||||
private void setQuoteContainer(Status status, final StatusActionListener listener) {
|
||||
if (status != null) {
|
||||
quoteContainer.setVisibility(View.VISIBLE);
|
||||
new QuoteInlineHelper(status, quoteContainer, listener).setupQuoteContainer();
|
||||
} else {
|
||||
quoteContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadImage(MediaPreviewImageView imageView, String previewUrl, String description,
|
||||
MetaData meta) {
|
||||
if (TextUtils.isEmpty(previewUrl)) {
|
||||
|
@ -568,6 +608,16 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
public void onEventAnimationStart(ImageView button, boolean buttonState) {
|
||||
}
|
||||
});
|
||||
|
||||
if (quoteButton != null) {
|
||||
quoteButton.setOnClickListener(view -> {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onQuote(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
moreButton.setOnClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
|
@ -607,6 +657,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
setAvatar(status.getAvatar(), status.getRebloggedAvatar(), status.isBot(), showBotOverlay, animateAvatar);
|
||||
setReblogged(status.isReblogged());
|
||||
setFavourited(status.isFavourited());
|
||||
setQuoteContainer(status.getQuote(), listener);
|
||||
List<Attachment> attachments = status.getAttachments();
|
||||
boolean sensitive = status.isSensitive();
|
||||
if (mediaPreviewEnabled) {
|
||||
|
@ -631,8 +682,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
setupButtons(listener, status.getSenderId());
|
||||
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
|
||||
setQuoteEnabled(status.getRebloggingEnabled(), status.getVisibility());
|
||||
|
||||
setSpoilerAndContent(status.isExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getStatusEmojis(), listener);
|
||||
setSpoilerAndContent(status.isExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getStatusEmojis(), listener,
|
||||
status.getQuote() != null);
|
||||
|
||||
setDescriptionForStatus(status);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.keylesspalace.tusky.adapter;
|
|||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
@ -16,9 +17,14 @@ import android.widget.LinearLayout;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.ViewThreadActivity;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
|
@ -28,10 +34,8 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
|
|||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
|
||||
|
||||
|
@ -209,7 +213,22 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
|||
|
||||
}
|
||||
|
||||
cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext()));
|
||||
cardView.setOnClickListener(v -> {
|
||||
String url = card.getUrl();
|
||||
String regex = ".*/users/[^/]+/statuses/([0-9]+)";
|
||||
String replace = "$1";
|
||||
Pattern p = Pattern.compile(regex);
|
||||
Matcher m = p.matcher(url);
|
||||
if (m.find()) {
|
||||
String id = m.replaceAll(replace);
|
||||
Intent intent = new Intent(v.getContext(), ViewThreadActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
intent.putExtra("url", url);
|
||||
v.getContext().startActivity(intent);
|
||||
} else {
|
||||
LinkHelper.openLink(url, v.getContext());
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
cardView.setVisibility(View.GONE);
|
||||
|
|
|
@ -156,7 +156,8 @@ data class ConversationStatusEntity(
|
|||
application = null,
|
||||
pinned = false,
|
||||
poll = poll,
|
||||
card = null)
|
||||
card = null,
|
||||
quote = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
|
||||
setupButtons(listener, account.getId());
|
||||
|
||||
setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getEmojis(), listener);
|
||||
setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getEmojis(), listener, false);
|
||||
|
||||
setConversationName(conversation.getAccounts());
|
||||
|
||||
|
|
|
@ -117,6 +117,10 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
viewModel.favourite(favourite, position)
|
||||
}
|
||||
|
||||
override fun onQuote(position: Int) {
|
||||
// its impossible to quote private messages
|
||||
}
|
||||
|
||||
override fun onMore(view: View, position: Int) {
|
||||
viewModel.conversations.value?.getOrNull(position)?.lastStatus?.let {
|
||||
more(it.toStatus(), view, position)
|
||||
|
|
|
@ -83,7 +83,8 @@ class StatusViewHolder(itemView: View,
|
|||
viewState.isContentShow(status.id, status.sensitive), status.spoilerText)
|
||||
|
||||
if (status.spoilerText.isBlank()) {
|
||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler,
|
||||
status.quote != null)
|
||||
itemView.statusContentWarningButton.hide()
|
||||
itemView.statusContentWarningDescription.hide()
|
||||
} else {
|
||||
|
@ -96,10 +97,12 @@ class StatusViewHolder(itemView: View,
|
|||
status()?.let { status ->
|
||||
itemView.statusContentWarningDescription.invalidate()
|
||||
viewState.setContentShow(status.id, isViewChecked)
|
||||
setTextVisible(isViewChecked, status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(isViewChecked, status.content, status.mentions, status.emojis, adapterHandler,
|
||||
status.quote != null)
|
||||
}
|
||||
}
|
||||
setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.emojis, adapterHandler)
|
||||
setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.emojis, adapterHandler,
|
||||
status.quote != null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,10 +112,11 @@ class StatusViewHolder(itemView: View,
|
|||
content: Spanned,
|
||||
mentions: Array<Status.Mention>?,
|
||||
emojis: List<Emoji>,
|
||||
listener: LinkListener) {
|
||||
listener: LinkListener,
|
||||
removeQuote: Boolean) {
|
||||
if (expanded) {
|
||||
val emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, itemView.statusContent)
|
||||
LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener)
|
||||
LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener, removeQuote)
|
||||
} else {
|
||||
LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener)
|
||||
}
|
||||
|
|
|
@ -94,6 +94,12 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
}
|
||||
}
|
||||
|
||||
override fun onQuote(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let { status ->
|
||||
quote(status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMore(view: View, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let {
|
||||
more(it, view, position)
|
||||
|
@ -200,6 +206,28 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
requireActivity().startActivity(intent)
|
||||
}
|
||||
|
||||
private fun quote(status: Status) {
|
||||
val id = status.actionableId
|
||||
val actionableStatus = status.actionableStatus
|
||||
val visibility = actionableStatus.visibility
|
||||
val url = actionableStatus.url
|
||||
val mentions = actionableStatus.mentions
|
||||
val mentionedUsernames = LinkedHashSet<String>()
|
||||
mentionedUsernames.add(actionableStatus.account.username)
|
||||
val loggedInUsername = viewModel.activeAccount?.username
|
||||
for ((_, _, username) in mentions) {
|
||||
mentionedUsernames.add(username)
|
||||
}
|
||||
mentionedUsernames.remove(loggedInUsername)
|
||||
val intent = ComposeActivity.IntentBuilder()
|
||||
.quoteId(id)
|
||||
.quoteUrl(url)
|
||||
.replyVisibility(visibility)
|
||||
.mentionedUsernames(mentionedUsernames)
|
||||
.build(context)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun more(status: Status, view: View, position: Int) {
|
||||
val id = status.actionableId
|
||||
val accountId = status.actionableStatus.account.id
|
||||
|
|
|
@ -26,7 +26,8 @@ data class NewStatus(
|
|||
val visibility: String,
|
||||
val sensitive: Boolean,
|
||||
@SerializedName("media_ids") val mediaIds: List<String>?,
|
||||
val poll: NewPoll?
|
||||
val poll: NewPoll?,
|
||||
@SerializedName("quote_id") val quoteId: String?
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
|
|
|
@ -43,7 +43,8 @@ data class Status(
|
|||
val application: Application?,
|
||||
var pinned: Boolean?,
|
||||
val poll: Poll?,
|
||||
val card: Card?
|
||||
val card: Card?,
|
||||
val quote: Status?
|
||||
) {
|
||||
|
||||
val actionableId: String
|
||||
|
|
|
@ -462,6 +462,11 @@ public class NotificationsFragment extends SFragment implements
|
|||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuote(int position) {
|
||||
super.quote(notifications.get(position).asRight().getStatus());
|
||||
}
|
||||
|
||||
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
|
||||
final Notification notification = notifications.get(position).asRight();
|
||||
final Status status = notification.getStatus();
|
||||
|
|
|
@ -163,6 +163,35 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
|||
getActivity().startActivity(intent);
|
||||
}
|
||||
|
||||
protected void quote(Status status) {
|
||||
String id = status.getActionableId();
|
||||
Status actionableStatus = status.getActionableStatus();
|
||||
Status.Visibility visibility = actionableStatus.getVisibility();
|
||||
String url = actionableStatus.getUrl();
|
||||
Status.Mention[] mentions = actionableStatus.getMentions();
|
||||
Set<String> mentionedUsernames = new LinkedHashSet<>();
|
||||
mentionedUsernames.add(actionableStatus.getAccount().getUsername());
|
||||
String loggedInUsername = null;
|
||||
AccountEntity activeAccount = accountManager.getActiveAccount();
|
||||
if(activeAccount != null) {
|
||||
loggedInUsername = activeAccount.getUsername();
|
||||
}
|
||||
for (Status.Mention mention : mentions) {
|
||||
mentionedUsernames.add(mention.getUsername());
|
||||
}
|
||||
mentionedUsernames.remove(loggedInUsername);
|
||||
if (status.getReblog() != null) {
|
||||
url = status.getReblog().getUrl();
|
||||
}
|
||||
Intent intent = new ComposeActivity.IntentBuilder()
|
||||
.quoteId(id)
|
||||
.quoteUrl(url)
|
||||
.replyVisibility(visibility)
|
||||
.mentionedUsernames(mentionedUsernames)
|
||||
.build(getContext());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
protected void more(@NonNull final Status status, View view, final int position) {
|
||||
final String id = status.getActionableId();
|
||||
final String accountId = status.getActionableStatus().getAccount().getId();
|
||||
|
|
|
@ -631,6 +631,11 @@ public class TimelineFragment extends SFragment implements
|
|||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuote(int position) {
|
||||
super.quote(statuses.get(position).asRight());
|
||||
}
|
||||
|
||||
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
|
||||
|
||||
final Status status = statuses.get(position).asRight();
|
||||
|
|
|
@ -257,6 +257,11 @@ public final class ViewThreadFragment extends SFragment implements
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuote(int position) {
|
||||
super.quote(statuses.get(position));
|
||||
}
|
||||
|
||||
private void updateStatus(int position, Status status) {
|
||||
if (position >= 0 && position < statuses.size()) {
|
||||
|
||||
|
|
|
@ -17,15 +17,16 @@ package com.keylesspalace.tusky.interfaces;
|
|||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface StatusActionListener extends LinkListener {
|
||||
void onReply(int position);
|
||||
void onReblog(final boolean reblog, final int position);
|
||||
void onFavourite(final boolean favourite, final int position);
|
||||
void onQuote(int position);
|
||||
void onMore(@NonNull View view, final int position);
|
||||
void onViewMedia(int position, int attachmentIndex, @Nullable View view);
|
||||
void onViewThread(int position);
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
package com.keylesspalace.tusky.network;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.keylesspalace.tusky.entity.AccessToken;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.AppCredentials;
|
||||
|
@ -37,7 +39,6 @@ import com.keylesspalace.tusky.entity.StatusContext;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Single;
|
||||
import okhttp3.MultipartBody;
|
||||
|
|
|
@ -18,11 +18,11 @@ package com.keylesspalace.tusky.receiver
|
|||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.RemoteInput
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.util.Log
|
||||
import com.keylesspalace.tusky.ComposeActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
|
@ -96,7 +96,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null, account, 0)
|
||||
null, null, account, 0)
|
||||
|
||||
context.startService(sendIntent)
|
||||
|
||||
|
|
|
@ -229,7 +229,8 @@ class TimelineRepositoryImpl(
|
|||
application = application,
|
||||
pinned = false,
|
||||
poll = poll,
|
||||
card = null
|
||||
card = null,
|
||||
quote = null
|
||||
)
|
||||
}
|
||||
val status = if (reblog != null) {
|
||||
|
@ -255,7 +256,8 @@ class TimelineRepositoryImpl(
|
|||
application = null,
|
||||
pinned = false,
|
||||
poll = null,
|
||||
card = null
|
||||
card = null,
|
||||
quote = null
|
||||
)
|
||||
} else {
|
||||
Status(
|
||||
|
@ -280,7 +282,8 @@ class TimelineRepositoryImpl(
|
|||
application = application,
|
||||
pinned = false,
|
||||
poll = poll,
|
||||
card = null
|
||||
card = null,
|
||||
quote = null
|
||||
)
|
||||
}
|
||||
return Either.Right(status)
|
||||
|
|
|
@ -140,7 +140,8 @@ class SendTootService : Service(), Injectable {
|
|||
tootToSend.visibility,
|
||||
tootToSend.sensitive,
|
||||
tootToSend.mediaIds,
|
||||
tootToSend.poll
|
||||
tootToSend.poll,
|
||||
tootToSend.quoteId
|
||||
)
|
||||
|
||||
val sendCall = mastodonApi.createStatus(
|
||||
|
@ -289,6 +290,7 @@ class SendTootService : Service(), Injectable {
|
|||
replyingStatusContent: String?,
|
||||
replyingStatusAuthorUsername: String?,
|
||||
savedJsonUrls: String?,
|
||||
quoteId: String?,
|
||||
account: AccountEntity,
|
||||
savedTootUid: Int
|
||||
): Intent {
|
||||
|
@ -308,6 +310,7 @@ class SendTootService : Service(), Injectable {
|
|||
replyingStatusContent,
|
||||
replyingStatusAuthorUsername,
|
||||
savedJsonUrls,
|
||||
quoteId,
|
||||
account.id,
|
||||
savedTootUid,
|
||||
idempotencyKey,
|
||||
|
@ -351,6 +354,7 @@ data class TootToSend(val text: String,
|
|||
val replyingStatusContent: String?,
|
||||
val replyingStatusAuthorUsername: String?,
|
||||
val savedJsonUrls: String?,
|
||||
val quoteId: String?,
|
||||
val accountId: Long,
|
||||
val savedTootUid: Int,
|
||||
val idempotencyKey: String,
|
||||
|
|
|
@ -20,10 +20,6 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
|
@ -33,11 +29,14 @@ import android.util.Log;
|
|||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
|
||||
import java.lang.CharSequence;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
|
@ -69,7 +68,8 @@ public class LinkHelper {
|
|||
* @param listener to notify about particular spans that are clicked
|
||||
*/
|
||||
public static void setClickableText(TextView view, Spanned content,
|
||||
@Nullable Status.Mention[] mentions, final LinkListener listener) {
|
||||
@Nullable Status.Mention[] mentions, final LinkListener listener,
|
||||
boolean removeQuote) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||
for (URLSpan span : urlSpans) {
|
||||
|
@ -126,6 +126,13 @@ public class LinkHelper {
|
|||
builder.subSequence(end, end + 1).toString().equals("\n")){
|
||||
builder.insert(end, "\u200B");
|
||||
}
|
||||
|
||||
if (start >= 13 && end < builder.length() && removeQuote) {
|
||||
if (builder.subSequence(start - 13, start).toString().equals("\n~~~~~~~~~~\n[")
|
||||
&& builder.subSequence(end, end + 1).toString().equals("]")) {
|
||||
builder.delete(start - 13, end + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.setText(builder);
|
||||
|
|
|
@ -64,6 +64,7 @@ public final class ViewDataUtils {
|
|||
.setPoll(visibleStatus.getPoll())
|
||||
.setCard(visibleStatus.getCard())
|
||||
.setIsBot(visibleStatus.getAccount().getBot())
|
||||
.setQuote(visibleStatus.getQuote())
|
||||
.createStatusViewData();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
package com.keylesspalace.tusky.viewdata;
|
||||
|
||||
import android.os.Build;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Attachment;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
|
@ -92,6 +93,8 @@ public abstract class StatusViewData {
|
|||
private final PollViewData poll;
|
||||
private final boolean isBot;
|
||||
|
||||
private final Status quote;
|
||||
|
||||
public Concrete(String id, Spanned content, boolean reblogged, boolean favourited,
|
||||
@Nullable String spoilerText, Status.Visibility visibility, List<Attachment> attachments,
|
||||
@Nullable String rebloggedByUsername, @Nullable String rebloggedAvatar, boolean sensitive, boolean isExpanded,
|
||||
|
@ -99,7 +102,7 @@ public abstract class StatusViewData {
|
|||
Date createdAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId,
|
||||
@Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
|
||||
Status.Application application, List<Emoji> statusEmojis, List<Emoji> accountEmojis, @Nullable Card card,
|
||||
boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot) {
|
||||
boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot, Status quote) {
|
||||
|
||||
this.id = id;
|
||||
if (Build.VERSION.SDK_INT == 23) {
|
||||
|
@ -138,6 +141,7 @@ public abstract class StatusViewData {
|
|||
this.isCollapsed = isCollapsed;
|
||||
this.poll = poll;
|
||||
this.isBot = isBot;
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -277,6 +281,10 @@ public abstract class StatusViewData {
|
|||
return poll;
|
||||
}
|
||||
|
||||
public Status getQuote() {
|
||||
return quote;
|
||||
}
|
||||
|
||||
@Override public long getViewDataId() {
|
||||
// Chance of collision is super low and impact of mistake is low as well
|
||||
return id.hashCode();
|
||||
|
@ -313,8 +321,9 @@ public abstract class StatusViewData {
|
|||
Objects.equals(statusEmojis, concrete.statusEmojis) &&
|
||||
Objects.equals(accountEmojis, concrete.accountEmojis) &&
|
||||
Objects.equals(card, concrete.card) &&
|
||||
Objects.equals(poll, concrete.poll)
|
||||
&& isCollapsed == concrete.isCollapsed;
|
||||
Objects.equals(poll, concrete.poll) &&
|
||||
isCollapsed == concrete.isCollapsed &&
|
||||
Objects.equals(quote, concrete.quote);
|
||||
}
|
||||
|
||||
static Spanned replaceCrashingCharacters(Spanned content) {
|
||||
|
@ -420,6 +429,7 @@ public abstract class StatusViewData {
|
|||
private boolean isCollapsed; /** Whether the status is shown partially or fully */
|
||||
private PollViewData poll;
|
||||
private boolean isBot;
|
||||
private Status quote;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
@ -455,6 +465,7 @@ public abstract class StatusViewData {
|
|||
isCollapsed = viewData.isCollapsed();
|
||||
poll = viewData.poll;
|
||||
isBot = viewData.isBot();
|
||||
quote = viewData.getQuote();
|
||||
}
|
||||
|
||||
public Builder setId(String id) {
|
||||
|
@ -621,6 +632,11 @@ public abstract class StatusViewData {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setQuote(Status quote){
|
||||
this.quote = quote;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatusViewData.Concrete createStatusViewData() {
|
||||
if (this.statusEmojis == null) statusEmojis = Collections.emptyList();
|
||||
if (this.accountEmojis == null) accountEmojis = Collections.emptyList();
|
||||
|
@ -630,7 +646,7 @@ public abstract class StatusViewData {
|
|||
attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
|
||||
isShowingContent, userFullName, nickname, avatar, createdAt, reblogsCount,
|
||||
favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application,
|
||||
statusEmojis, accountEmojis, card, isCollapsible, isCollapsed, poll, isBot);
|
||||
statusEmojis, accountEmojis, card, isCollapsible, isCollapsed, poll, isBot, quote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package net.accelf.yuito;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QuoteInlineHelper {
|
||||
private Status quoteStatus;
|
||||
|
||||
private View quoteContainer;
|
||||
private ImageView quoteAvatar;
|
||||
private TextView quoteDisplayName;
|
||||
private TextView quoteUsername;
|
||||
private TextView quoteContentWarningDescription;
|
||||
private ToggleButton quoteContentWarningButton;
|
||||
private TextView quoteContent;
|
||||
private TextView quoteMedia;
|
||||
|
||||
private LinkListener listener;
|
||||
|
||||
public QuoteInlineHelper(Status status, View container, LinkListener listener) {
|
||||
quoteStatus = status;
|
||||
quoteContainer = container;
|
||||
quoteAvatar = container.findViewById(R.id.status_quote_inline_avatar);
|
||||
quoteDisplayName = container.findViewById(R.id.status_quote_inline_display_name);
|
||||
quoteUsername = container.findViewById(R.id.status_quote_inline_username);
|
||||
quoteContentWarningDescription = container.findViewById(R.id.status_quote_inline_content_warning_description);
|
||||
quoteContentWarningButton = container.findViewById(R.id.status_quote_inline_content_warning_button);
|
||||
quoteContent = container.findViewById(R.id.status_quote_inline_content);
|
||||
quoteMedia = container.findViewById(R.id.status_quote_inline_media);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void setDisplayName(String name, List<Emoji> customEmojis) {
|
||||
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(name, customEmojis, quoteDisplayName);
|
||||
quoteDisplayName.setText(emojifiedName);
|
||||
}
|
||||
|
||||
private void setUsername(String name) {
|
||||
Context context = quoteUsername.getContext();
|
||||
String format = context.getString(R.string.status_username_format);
|
||||
String usernameText = String.format(format, name);
|
||||
quoteUsername.setText(usernameText);
|
||||
}
|
||||
|
||||
private void setContent(Spanned content, Status.Mention[] mentions, List<Emoji> emojis,
|
||||
LinkListener listener) {
|
||||
Spanned singleLineText = SpannedTextHelper.replaceSpanned(content);
|
||||
Spanned emojifiedText = CustomEmojiHelper.emojifyText(singleLineText, emojis, quoteContent);
|
||||
LinkHelper.setClickableText(quoteContent, emojifiedText, mentions, listener, false);
|
||||
}
|
||||
|
||||
private void setAvatar(String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
quoteAvatar.setImageResource(R.drawable.avatar_default);
|
||||
} else {
|
||||
Glide.with(quoteAvatar.getContext())
|
||||
.load(url)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.into(quoteAvatar);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSpoilerText(String spoilerText, List<Emoji> emojis) {
|
||||
CharSequence emojiSpoiler =
|
||||
CustomEmojiHelper.emojifyString(spoilerText, emojis, quoteContentWarningDescription);
|
||||
quoteContentWarningDescription.setText(emojiSpoiler);
|
||||
quoteContentWarningDescription.setVisibility(View.VISIBLE);
|
||||
quoteContentWarningButton.setVisibility(View.VISIBLE);
|
||||
quoteContentWarningButton.setChecked(false);
|
||||
quoteContentWarningButton.setOnCheckedChangeListener((buttonView, isChecked)
|
||||
-> quoteContent.setVisibility(isChecked ? View.VISIBLE : View.GONE));
|
||||
quoteContent.setVisibility(View.GONE);
|
||||
|
||||
}
|
||||
|
||||
private void hideSpoilerText() {
|
||||
quoteContentWarningDescription.setVisibility(View.GONE);
|
||||
quoteContentWarningButton.setVisibility(View.GONE);
|
||||
quoteContent.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setOnClickListener(String accountId, String statusUrl) {
|
||||
quoteAvatar.setOnClickListener(view -> listener.onViewAccount(accountId));
|
||||
quoteDisplayName.setOnClickListener(view -> listener.onViewAccount(accountId));
|
||||
quoteUsername.setOnClickListener(view -> listener.onViewAccount(accountId));
|
||||
quoteContent.setOnClickListener(view -> listener.onViewUrl(statusUrl));
|
||||
quoteMedia.setOnClickListener(view -> listener.onViewUrl(statusUrl));
|
||||
quoteContainer.setOnClickListener(view -> listener.onViewUrl(statusUrl));
|
||||
}
|
||||
|
||||
public void setupQuoteContainer() {
|
||||
Account account = quoteStatus.getAccount();
|
||||
setDisplayName(account.getDisplayName().equals("") ? account.getLocalUsername() : account.getDisplayName(), account.getEmojis());
|
||||
setUsername(account.getUsername());
|
||||
setContent(quoteStatus.getContent(), quoteStatus.getMentions(),
|
||||
quoteStatus.getEmojis(), listener);
|
||||
setAvatar(account.getAvatar());
|
||||
setOnClickListener(account.getId(), quoteStatus.getUrl());
|
||||
|
||||
if (quoteStatus.getSpoilerText().isEmpty()) {
|
||||
hideSpoilerText();
|
||||
} else {
|
||||
setSpoilerText(quoteStatus.getSpoilerText(), quoteStatus.getEmojis());
|
||||
}
|
||||
|
||||
if (quoteStatus.getAttachments().size() == 0) {
|
||||
quoteMedia.setVisibility(View.GONE);
|
||||
} else {
|
||||
quoteMedia.setVisibility(View.VISIBLE);
|
||||
quoteMedia.setText(quoteContainer.getContext().getString(R.string.status_quote_media,
|
||||
quoteStatus.getAttachments().size()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package net.accelf.yuito;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class SpannedTextHelper {
|
||||
|
||||
static Spanned replaceSpanned(Spanned targetText) {
|
||||
String targetString = targetText.toString();
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(targetText);
|
||||
Pattern pattern = Pattern.compile("\n");
|
||||
Matcher matcher = pattern.matcher(targetString);
|
||||
while (matcher.find()) {
|
||||
builder.replace(matcher.start(), matcher.end(), " ");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/toolbar_icon_dark"
|
||||
android:pathData="m 20.9375,2.375 h -5.5 c -1.138672,0 -2.0625,0.923828 -2.0625,2.0625 v 5.5 c 0,1.138672 0.923828,2.0625 2.0625,2.0625 h 3.4375 v 2.75 c 0,1.516797 -1.233203,2.75 -2.75,2.75 H 15.78125 C 15.209766,17.5 14.75,17.959766 14.75,18.53125 v 2.0625 c 0,0.571484 0.459766,1.03125 1.03125,1.03125 H 16.125 C 19.923437,21.625 23,18.548437 23,14.75 V 4.4375 C 23,3.298828 22.076172,2.375 20.9375,2.375 Z m -12.375,0 h -5.5 C 1.9238281,2.375 1,3.298828 1,4.4375 v 5.5 C 1,11.076172 1.9238281,12 3.0625,12 H 6.5 v 2.75 c 0,1.516797 -1.2332031,2.75 -2.75,2.75 H 3.40625 C 2.8347656,17.5 2.375,17.959766 2.375,18.53125 v 2.0625 c 0,0.571484 0.4597656,1.03125 1.03125,1.03125 H 3.75 c 3.7984375,0 6.875,-3.076563 6.875,-6.875 V 4.4375 C 10.625,3.298828 9.701172,2.375 8.5625,2.375 Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/status_reblog_button_disabled_dark"
|
||||
android:pathData="m 20.9375,2.375 h -5.5 c -1.138672,0 -2.0625,0.923828 -2.0625,2.0625 v 5.5 c 0,1.138672 0.923828,2.0625 2.0625,2.0625 h 3.4375 v 2.75 c 0,1.516797 -1.233203,2.75 -2.75,2.75 H 15.78125 C 15.209766,17.5 14.75,17.959766 14.75,18.53125 v 2.0625 c 0,0.571484 0.459766,1.03125 1.03125,1.03125 H 16.125 C 19.923437,21.625 23,18.548437 23,14.75 V 4.4375 C 23,3.298828 22.076172,2.375 20.9375,2.375 Z m -12.375,0 h -5.5 C 1.9238281,2.375 1,3.298828 1,4.4375 v 5.5 C 1,11.076172 1.9238281,12 3.0625,12 H 6.5 v 2.75 c 0,1.516797 -1.2332031,2.75 -2.75,2.75 H 3.40625 C 2.8347656,17.5 2.375,17.959766 2.375,18.53125 v 2.0625 c 0,0.571484 0.4597656,1.03125 1.03125,1.03125 H 3.75 c 3.7984375,0 6.875,-3.076563 6.875,-6.875 V 4.4375 C 10.625,3.298828 9.701172,2.375 8.5625,2.375 Z" />
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#888888" />
|
||||
|
||||
</shape>
|
|
@ -90,6 +90,18 @@
|
|||
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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/composeQuoteView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:textSize="?attr/status_text_small"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/composeContentWarningBar"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -177,6 +177,18 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/status_content"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<include
|
||||
android:id="@+id/status_quote_inline_container"
|
||||
layout="@layout/view_quote_inline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_toggle_content" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/status_media_preview_container"
|
||||
android:layout_width="0dp"
|
||||
|
@ -185,7 +197,7 @@
|
|||
android:importantForAccessibility="noHideDescendants"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_toggle_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_quote_inline_container"
|
||||
tools:visibility="gone">
|
||||
|
||||
<com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
|
@ -449,7 +461,7 @@
|
|||
android:contentDescription="@string/action_favourite"
|
||||
android:importantForAccessibility="no"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_more"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_quote"
|
||||
app:layout_constraintStart_toEndOf="@id/status_inset"
|
||||
app:layout_constraintTop_toTopOf="@id/status_inset"
|
||||
sparkbutton:activeImage="?attr/status_favourite_active_drawable"
|
||||
|
@ -458,6 +470,21 @@
|
|||
sparkbutton:primaryColor="@color/tusky_orange"
|
||||
sparkbutton:secondaryColor="@color/tusky_orange_light" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_quote"
|
||||
style="?attr/image_button_style"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:contentDescription="@string/action_quote"
|
||||
android:importantForAccessibility="no"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_reply"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_more"
|
||||
app:layout_constraintStart_toEndOf="@id/status_favourite"
|
||||
app:layout_constraintTop_toTopOf="@id/status_reply"
|
||||
app:srcCompat="@drawable/ic_quote_disabled_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_more"
|
||||
style="?attr/image_button_style"
|
||||
|
@ -469,7 +496,7 @@
|
|||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_reply"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/status_favourite"
|
||||
app:layout_constraintStart_toEndOf="@id/status_quote"
|
||||
app:layout_constraintTop_toTopOf="@id/status_reply"
|
||||
app:srcCompat="@drawable/ic_more_horiz_24dp" />
|
||||
|
||||
|
|
|
@ -129,6 +129,18 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/status_content_warning_button"
|
||||
tools:text="Status content. Can be pretty long. " />
|
||||
|
||||
<include
|
||||
android:id="@+id/status_quote_inline_container"
|
||||
layout="@layout/view_quote_inline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -139,7 +151,7 @@
|
|||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="80dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status_quote_inline_container"
|
||||
tools:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
|
@ -539,7 +551,7 @@
|
|||
android:contentDescription="@string/action_favourite"
|
||||
android:importantForAccessibility="no"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_more"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_quote"
|
||||
app:layout_constraintStart_toEndOf="@id/status_inset"
|
||||
app:layout_constraintTop_toTopOf="@id/status_inset"
|
||||
sparkbutton:activeImage="?attr/status_favourite_active_drawable"
|
||||
|
@ -548,6 +560,20 @@
|
|||
sparkbutton:primaryColor="@color/tusky_orange"
|
||||
sparkbutton:secondaryColor="@color/tusky_orange_light" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_quote"
|
||||
style="?attr/image_button_style"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/action_quote"
|
||||
android:importantForAccessibility="no"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_reply"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_more"
|
||||
app:layout_constraintStart_toEndOf="@id/status_favourite"
|
||||
app:layout_constraintTop_toTopOf="@id/status_reply"
|
||||
app:srcCompat="@drawable/ic_quote_disabled_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_more"
|
||||
style="?attr/image_button_style"
|
||||
|
@ -558,7 +584,7 @@
|
|||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_reply"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/status_favourite"
|
||||
app:layout_constraintStart_toEndOf="@id/status_quote"
|
||||
app:layout_constraintTop_toTopOf="@id/status_reply"
|
||||
app:srcCompat="@drawable/ic_more_horiz_24dp" />
|
||||
|
||||
|
|
|
@ -137,6 +137,15 @@
|
|||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/status_quote_inline_container"
|
||||
layout="@layout/view_quote_inline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/button_toggle_notification_content"
|
||||
android:layout_toEndOf="@id/notification_status_avatar"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/notification_status_avatar"
|
||||
android:layout_width="48dp"
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/quote_inline_frame"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_quote_inline_avatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@null"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/avatar_default" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/status_quote_inline_display_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:textStyle="normal|bold"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_quote_inline_avatar"
|
||||
app:layout_constraintStart_toEndOf="@id/status_quote_inline_avatar"
|
||||
app:layout_constraintTop_toTopOf="@id/status_quote_inline_avatar"
|
||||
tools:text="Display Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_quote_inline_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:importantForAccessibility="no"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_quote_inline_avatar"
|
||||
app:layout_constraintStart_toEndOf="@id/status_quote_inline_display_name"
|
||||
app:layout_constraintTop_toTopOf="@id/status_quote_inline_avatar"
|
||||
tools:text="\@ars42525\@odakyu.app" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/status_quote_inline_content_warning_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_quote_inline_avatar"
|
||||
tools:text="CW"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/status_quote_inline_content_warning_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/content_warning_button"
|
||||
android:importantForAccessibility="no"
|
||||
android:minWidth="150dp"
|
||||
android:minHeight="0dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textAllCaps="true"
|
||||
android:textOff="@string/status_content_warning_show_more"
|
||||
android:textOn="@string/status_content_warning_show_less"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toEndOf="@id/status_quote_inline_content_warning_description"
|
||||
app:layout_constraintTop_toTopOf="@id/status_quote_inline_content_warning_description"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/status_quote_inline_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:maxLines="8"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_quote_inline_content_warning_button"
|
||||
tools:text="This is a status" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_quote_inline_media"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:text="@string/status_quote_media"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_quote_inline_content"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -46,6 +46,7 @@
|
|||
<string name="status_content_warning_show_less">続きを隠す</string>
|
||||
<string name="status_content_show_more">続きを読む</string>
|
||||
<string name="status_content_show_less">閉じる</string>
|
||||
<string name="status_quote_media">このトゥートには%d件のメディアが存在します。</string>
|
||||
<string name="message_empty">何もありません。</string>
|
||||
<string name="footer_empty">現在トゥートはありません。更新するにはプルダウンしてください!</string>
|
||||
<string name="notification_reblog_format">%sさんがトゥートをブーストしました</string>
|
||||
|
@ -57,6 +58,7 @@
|
|||
<string name="action_reply">返信</string>
|
||||
<string name="action_reblog">ブースト</string>
|
||||
<string name="action_favourite">お気に入り</string>
|
||||
<string name="action_quote">引用</string>
|
||||
<string name="action_more">その他</string>
|
||||
<string name="action_compose">新規投稿</string>
|
||||
<string name="action_login">Mastodonでログイン</string>
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
<item name="status_reblog_direct_drawable">@drawable/reblog_direct_dark</item>
|
||||
<item name="status_favourite_active_drawable">@drawable/favourite_active_dark</item>
|
||||
<item name="status_favourite_inactive_drawable">@drawable/favourite_inactive_dark</item>
|
||||
<item name="status_quote_drawable">@drawable/ic_quote_24dp</item>
|
||||
<item name="status_quote_disabled_drawable">@drawable/ic_quote_disabled_24dp</item>
|
||||
<item name="content_warning_button">@drawable/toggle_small</item>
|
||||
<item name="sensitive_media_warning_background_color">@color/color_background_dark</item>
|
||||
<item name="media_preview_unloaded_drawable">@drawable/media_preview_unloaded_dark</item>
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
<attr name="status_reblog_direct_drawable" format="reference" />
|
||||
<attr name="status_favourite_active_drawable" format="reference" />
|
||||
<attr name="status_favourite_inactive_drawable" format="reference" />
|
||||
<attr name="status_quote_drawable" format="reference" />
|
||||
<attr name="status_quote_disabled_drawable" format="reference" />
|
||||
<attr name="content_warning_button" format="reference" />
|
||||
<attr name="sensitive_media_warning_background_color" format="reference|color" />
|
||||
<attr name="media_preview_unloaded_drawable" format="reference" />
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
<string name="status_content_warning_show_less">Show Less</string>
|
||||
<string name="status_content_show_more">Expand</string>
|
||||
<string name="status_content_show_less">Collapse</string>
|
||||
<string name="status_quote_media">This toot has %d media(s).</string>
|
||||
|
||||
<string name="message_empty">Nothing here.</string>
|
||||
<string name="footer_empty">Nothing here. Pull down to refresh!</string>
|
||||
|
@ -124,6 +125,7 @@
|
|||
<string name="action_open_reblogger">Open boost author</string>
|
||||
<string name="action_open_reblogged_by">Show boosts</string>
|
||||
<string name="action_open_faved_by">Show favorites</string>
|
||||
<string name="action_quote">Quote</string>
|
||||
|
||||
<string name="title_hashtags_dialog">Hashtags</string>
|
||||
<string name="title_mentions_dialog">Mentions</string>
|
||||
|
@ -321,6 +323,7 @@
|
|||
<string name="pref_title_alway_open_spoiler">Always expand toots marked with content warnings</string>
|
||||
<string name="title_media">Media</string>
|
||||
<string name="replying_to">Replying to @%s</string>
|
||||
<string name="quote_to">Quote : %s</string>
|
||||
<string name="load_more_placeholder_text">load more</string>
|
||||
|
||||
<string name="pref_title_public_filter_keywords">Public timelines</string>
|
||||
|
|
|
@ -87,6 +87,8 @@
|
|||
<item name="status_reblog_direct_drawable">@drawable/reblog_direct_light</item>
|
||||
<item name="status_favourite_active_drawable">@drawable/favourite_active_light</item>
|
||||
<item name="status_favourite_inactive_drawable">@drawable/favourite_inactive_light</item>
|
||||
<item name="status_quote_drawable">@drawable/ic_quote_24dp</item>
|
||||
<item name="status_quote_disabled_drawable">@drawable/ic_quote_disabled_24dp</item>
|
||||
<item name="content_warning_button">@drawable/toggle_small_light</item>
|
||||
<item name="sensitive_media_warning_background_color">
|
||||
@color/sensitive_media_warning_background_light
|
||||
|
|
|
@ -316,7 +316,8 @@ class TimelineRepositoryTest {
|
|||
reblog = null,
|
||||
url = "http://example.com/statuses/$id",
|
||||
poll = null,
|
||||
card = null
|
||||
card = null,
|
||||
quote = null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue