Merge remote-tracking branch 'tuskyapp/develop'

This commit is contained in:
kyori19 2020-05-16 18:01:15 +09:00
commit 0e8aa1f48b
58 changed files with 259 additions and 313 deletions

View File

@ -360,9 +360,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
val usernameFormatted = getString(R.string.status_username_format, account.username)
accountUsernameTextView.text = usernameFormatted
accountDisplayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, accountDisplayNameTextView)
accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView)
val emojifiedNote = CustomEmojiHelper.emojifyText(account.note, account.emojis, accountNoteTextView)
val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView)
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this, false)
// accountFieldAdapter.fields = account.fields ?: emptyList()
@ -423,7 +423,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
private fun updateToolbar() {
loadedAccount?.let { account ->
val emojifiedName = CustomEmojiHelper.emojifyString(account.name, account.emojis, accountToolbar)
val emojifiedName = account.name.emojify(account.emojis, accountToolbar)
try {
supportActionBar?.title = EmojiCompat.get().process(emojifiedName)

View File

@ -209,7 +209,7 @@ class AccountsInListFragment : DialogFragment(), Injectable {
}
fun bind(account: Account) {
displayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, displayNameTextView)
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView)
usernameTextView.text = account.username
loadAvatar(account.avatar, avatar, radius, animateAvatar)
}
@ -252,7 +252,7 @@ class AccountsInListFragment : DialogFragment(), Injectable {
override val containerView = itemView
fun bind(account: Account, inAList: Boolean) {
displayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, displayNameTextView)
displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView)
usernameTextView.text = account.username
loadAvatar(account.avatar, avatar, radius, animateAvatar)

View File

@ -707,7 +707,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private fun updateProfiles() {
val profiles: MutableList<IProfile> = accountManager.getAllAccountsOrderedByActive().map { acc ->
val emojifiedName = EmojiCompat.get().process(CustomEmojiHelper.emojifyString(acc.displayName, acc.emojis, header))
val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header))
ProfileDrawerItem().apply {
isSelected = acc.isActive

View File

@ -45,7 +45,7 @@ fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabD
LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) })
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) })
DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.ic_reblog_direct_24dp, { ConversationsFragment.newInstance() })
HASHTAG -> TabData(HASHTAG, R.string.hashtag, R.drawable.ic_hashtag, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.TAG, args.getOrNull(0).orEmpty()) }, arguments)
HASHTAG -> TabData(HASHTAG, R.string.hashtags, R.drawable.ic_hashtag, { args -> TimelineFragment.newHashtagInstance(args) }, arguments)
LIST -> TabData(LIST, R.string.list, R.drawable.ic_list, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, arguments)
else -> throw IllegalArgumentException("unknown tab type")
}

View File

@ -18,13 +18,16 @@ package com.keylesspalace.tusky
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.widget.FrameLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import at.connyduck.sparkbutton.helpers.Utils
import com.keylesspalace.tusky.adapter.ItemInteractionListener
import com.keylesspalace.tusky.adapter.ListSelectionAdapter
import com.keylesspalace.tusky.adapter.TabAdapter
@ -150,7 +153,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
actionButton.isExpanded = false
if (tab.id == HASHTAG) {
showEditHashtagDialog()
showAddHashtagDialog()
return
}
@ -173,19 +176,32 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
}
override fun onActionChipClicked(tab: TabData) {
showEditHashtagDialog(tab)
showAddHashtagDialog(tab)
}
private fun showEditHashtagDialog(tab: TabData? = null) {
override fun onChipClicked(tab: TabData, chipPosition: Int) {
val newArguments = tab.arguments.filterIndexed { i, _ -> i != chipPosition }
val newTab = tab.copy(arguments = newArguments)
val position = currentTabs.indexOf(tab)
currentTabs[position] = newTab
currentTabsAdapter.notifyItemChanged(position)
}
private fun showAddHashtagDialog(tab: TabData? = null) {
val frameLayout = FrameLayout(this)
val padding = Utils.dpToPx(this, 8)
frameLayout.updatePadding(left = padding, right = padding)
val editText = AppCompatEditText(this)
editText.setHint(R.string.edit_hashtag_hint)
editText.setText("")
editText.append(tab?.arguments?.first().orEmpty())
frameLayout.addView(editText)
val dialog = AlertDialog.Builder(this)
.setTitle(R.string.edit_hashtag_title)
.setView(editText)
.setTitle(R.string.add_hashtag_title)
.setView(frameLayout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_save) { _, _ ->
val input = editText.text.toString().trim()
@ -194,7 +210,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
} else {
val newTab = tab.copy(arguments = listOf(input))
val newTab = tab.copy(arguments = tab.arguments + input)
val position = currentTabs.indexOf(tab)
currentTabs[position] = newTab

View File

@ -33,6 +33,8 @@ import com.keylesspalace.tusky.fragment.TimelineFragment;
import net.accelf.yuito.QuickTootHelper;
import java.util.Collections;
import javax.inject.Inject;
import dagger.android.AndroidInjector;
@ -76,7 +78,7 @@ public class ViewTagActivity extends BottomSheetActivity implements HasAndroidIn
}
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = TimelineFragment.newInstance(TimelineFragment.Kind.TAG, hashtag);
Fragment fragment = TimelineFragment.newHashtagInstance(Collections.singletonList(hashtag));
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.commit();

View File

@ -26,9 +26,7 @@ import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Field
import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.Either
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_account_field.view.*
class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView.Adapter<AccountFieldAdapter.ViewHolder>() {
@ -57,10 +55,10 @@ class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView
viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
} else {
val field = proofOrField.asRight()
val emojifiedName = CustomEmojiHelper.emojifyString(field.name, emojis, viewHolder.nameTextView)
val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView)
viewHolder.nameTextView.text = emojifiedName
val emojifiedValue = CustomEmojiHelper.emojifyText(field.value, emojis, viewHolder.valueTextView)
val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView)
LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener, false)
if(field.verifiedAt != null) {

View File

@ -23,8 +23,7 @@ import android.widget.ArrayAdapter
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_autocomplete_account.view.*
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
@ -43,7 +42,7 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(co
val displayName = view.display_name
val avatar = view.avatar
username.text = account.fullName
displayName.text = CustomEmojiHelper.emojifyString(account.displayName, account.emojis, displayName)
displayName.text = account.displayName.emojify(account.emojis, displayName)
val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp)
val animateAvatar = PreferenceManager.getDefaultSharedPreferences(avatar.context)

View File

@ -40,7 +40,7 @@ public class AccountViewHolder extends RecyclerView.ViewHolder {
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());
username.setText(formattedUsername);
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName);
int avatarRadius = avatar.getContext().getResources()
.getDimensionPixelSize(R.dimen.avatar_radius_48dp);

View File

@ -86,7 +86,7 @@ public class BlocksAdapter extends AccountAdapter {
void setupWithAccount(Account account) {
id = account.getId();
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName);
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());

View File

@ -146,7 +146,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
account.getUsername()
);
accountViewHolder.username.setText(formattedUsername);
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(),
CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(),
account.getEmojis(), accountViewHolder.displayName);
accountViewHolder.displayName.setText(emojifiedName);

View File

@ -7,9 +7,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.interfaces.AccountActionListener
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.loadAvatar
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.item_follow_request_notification.view.*
internal class FollowRequestViewHolder(itemView: View, private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) {
@ -20,7 +18,7 @@ internal class FollowRequestViewHolder(itemView: View, private val showHeader: B
fun setupWithAccount(account: Account, formatter: BidiFormatter?) {
id = account.id
val wrappedName = formatter?.unicodeWrap(account.name) ?: account.name
val emojifiedName: CharSequence = CustomEmojiHelper.emojifyString(wrappedName, account.emojis, itemView)
val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView)
itemView.displayNameTextView.text = emojifiedName
if (showHeader) {
itemView.notificationTextView?.text = itemView.context.getString(R.string.notification_follow_request_format, emojifiedName)

View File

@ -71,7 +71,7 @@ public class MutesAdapter extends AccountAdapter {
void setupWithAccount(Account account) {
id = account.getId();
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(account.getName(), account.getEmojis(), displayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName);
displayName.setText(emojifiedName);
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.getUsername());

View File

@ -336,13 +336,13 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
String format = context.getString(R.string.notification_follow_format);
String wrappedDisplayName = bidiFormatter.unicodeWrap(account.getName());
String wholeMessage = String.format(format, wrappedDisplayName);
CharSequence emojifiedMessage = CustomEmojiHelper.emojifyString(wholeMessage, account.getEmojis(), message);
CharSequence emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message);
message.setText(emojifiedMessage);
String username = context.getString(R.string.status_username_format, account.getUsername());
usernameView.setText(username);
CharSequence emojifiedDisplayName = CustomEmojiHelper.emojifyString(wrappedDisplayName, account.getEmojis(), usernameView);
CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify(wrappedDisplayName, account.getEmojis(), usernameView);
displayNameView.setText(emojifiedDisplayName);
@ -420,7 +420,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
private void setDisplayName(String name, List<Emoji> emojis) {
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(name, emojis, displayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName);
displayName.setText(emojifiedName);
}
@ -504,7 +504,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage);
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
CharSequence emojifiedText = CustomEmojiHelper.emojifyText(str, notificationViewData.getAccount().getEmojis(), message);
CharSequence emojifiedText = CustomEmojiHelper.emojify(str, notificationViewData.getAccount().getEmojis(), message);
message.setText(emojifiedText);
if (statusViewData != null) {
@ -612,12 +612,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
statusContent.setFilters(NO_INPUT_FILTER);
}
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, statusContent);
CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, statusContent);
LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener,
statusViewData.getQuote() != null);
Spanned emojifiedContentWarning =
CustomEmojiHelper.emojifyString(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView);
CharSequence emojifiedContentWarning =
CustomEmojiHelper.emojify(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView);
contentWarningDescriptionTextView.setText(emojifiedContentWarning);
setQuoteContainer(statusViewData.getQuote(), listener, statusDisplayOptions);

View File

@ -25,8 +25,7 @@ import androidx.emoji.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewdata.PollOptionViewData
import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent
@ -78,7 +77,8 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
when(mode) {
RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(buildDescription(option.title, percent, holder.resultTextView.context), emojis, holder.resultTextView)
val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context)
.emojify(emojis, holder.resultTextView)
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100
@ -87,7 +87,7 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
}
SINGLE -> {
val emojifiedPollOptionText = CustomEmojiHelper.emojifyString(option.title, emojis, holder.radioButton)
val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton)
holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.radioButton.isChecked = option.selected
holder.radioButton.setOnClickListener {
@ -98,7 +98,7 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
}
}
MULTIPLE -> {
val emojifiedPollOptionText = CustomEmojiHelper.emojifyString(option.title, emojis, holder.checkBox)
val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox)
holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.checkBox.isChecked = option.selected
holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
@ -124,4 +124,4 @@ class PollViewHolder(view: View): RecyclerView.ViewHolder(view) {
val radioButton: RadioButton = view.findViewById(R.id.status_poll_radio_button)
val checkBox: CheckBox = view.findViewById(R.id.status_poll_checkbox)
}
}

View File

@ -190,7 +190,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
protected abstract int getMediaPreviewHeight(Context context);
protected void setDisplayName(String name, List<Emoji> customEmojis) {
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(name, customEmojis, displayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName);
displayName.setText(emojifiedName);
}
@ -215,7 +215,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
boolean removeQuote) {
boolean sensitive = !TextUtils.isEmpty(spoilerText);
if (sensitive) {
CharSequence emojiSpoiler = CustomEmojiHelper.emojifyString(spoilerText, emojis, contentWarningDescription);
CharSequence emojiSpoiler = CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription);
contentWarningDescription.setText(emojiSpoiler);
contentWarningDescription.setVisibility(View.VISIBLE);
contentWarningButton.setVisibility(View.VISIBLE);
@ -255,7 +255,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
final StatusActionListener listener,
boolean removeQuote) {
if (expanded) {
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, this.content);
CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content);
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener, removeQuote);
for (int i = 0; i < mediaLabels.length; ++i) {
updateMediaLabel(i, sensitive, expanded);

View File

@ -19,7 +19,9 @@ import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.size
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.keylesspalace.tusky.HASHTAG
import com.keylesspalace.tusky.LIST
import com.keylesspalace.tusky.R
@ -29,13 +31,13 @@ import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import kotlinx.android.synthetic.main.item_tab_preference.view.*
interface ItemInteractionListener {
fun onTabAdded(tab: TabData)
fun onTabRemoved(position: Int)
fun onStartDelete(viewHolder: RecyclerView.ViewHolder)
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
fun onActionChipClicked(tab: TabData)
fun onChipClicked(tab: TabData, chipPosition: Int)
}
class TabAdapter(private var data: List<TabData>,
@ -86,9 +88,9 @@ class TabAdapter(private var data: List<TabData>,
if (holder.itemView.removeButton != null) {
holder.itemView.removeButton.isEnabled = removeButtonEnabled
ThemeUtils.setDrawableTint(
holder.itemView.context,
holder.itemView.removeButton.drawable,
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
holder.itemView.context,
holder.itemView.removeButton.drawable,
(if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled)
)
}
@ -96,11 +98,38 @@ class TabAdapter(private var data: List<TabData>,
if (data[position].id == HASHTAG) {
holder.itemView.chipGroup.show()
holder.itemView.actionChip.text = data[position].arguments[0]
holder.itemView.actionChip.setChipIconResource(R.drawable.ic_edit_chip)
/*
* The chip group will always contain the actionChip (it is defined in the xml layout).
* The other dynamic chips are inserted in front of the actionChip.
* This code tries to reuse already added chips to reduce the number of Views created.
*/
data[position].arguments.forEachIndexed { i, arg ->
val chip = holder.itemView.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip?
?: Chip(context).apply {
text = arg
holder.itemView.chipGroup.addView(this, holder.itemView.chipGroup.size - 1)
}
chip.text = arg
if(data[position].arguments.size <= 1) {
chip.chipIcon = null
chip.setOnClickListener(null)
} else {
val cancelIcon = ThemeUtils.getTintedDrawable(context, R.drawable.ic_cancel_24dp, android.R.attr.textColorPrimary)
chip.chipIcon = cancelIcon
chip.setOnClickListener {
listener.onChipClicked(data[position], i)
}
}
}
while(holder.itemView.chipGroup.size - 1 > data[position].arguments.size) {
holder.itemView.chipGroup.removeViewAt(data[position].arguments.size - 1)
}
holder.itemView.actionChip.chipIcon = context.getDrawable(R.drawable.ic_edit_chip)
holder.itemView.actionChip.setOnClickListener {
listener.onActionChipClicked(data[position])
}

View File

@ -90,7 +90,7 @@ class StatusViewHolder(
itemView.statusContentWarningButton.hide()
itemView.statusContentWarningDescription.hide()
} else {
val emojiSpoiler = CustomEmojiHelper.emojifyString(status.spoilerText, status.emojis, itemView.statusContentWarningDescription)
val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription)
itemView.statusContentWarningDescription.text = emojiSpoiler
itemView.statusContentWarningDescription.show()
itemView.statusContentWarningButton.show()
@ -126,7 +126,7 @@ class StatusViewHolder(
listener: LinkListener,
removeQuote: Boolean) {
if (expanded) {
val emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, itemView.statusContent)
val emojifiedText = content.emojify(emojis, itemView.statusContent)
LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener, removeQuote)
} else {
LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener)

View File

@ -97,6 +97,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
import net.accelf.yuito.TimelineStreamingListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
@ -129,7 +130,8 @@ public class TimelineFragment extends SFragment implements
Injectable, ReselectableFragment, RefreshableFragment {
private static final String TAG = "TimelineF"; // logging tag
private static final String KIND_ARG = "kind";
private static final String HASHTAG_OR_ID_ARG = "hashtag_or_id";
private static final String ID_ARG = "id";
private static final String HASHTAGS_ARG = "hastags";
private static final String ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh";
private static final int LOAD_AT_ONCE = 30;
@ -173,7 +175,8 @@ public class TimelineFragment extends SFragment implements
private TimelineAdapter adapter;
private Kind kind;
private String hashtagOrId;
private String id;
private List<String> tags;
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private boolean filterRemoveReplies;
@ -220,25 +223,37 @@ public class TimelineFragment extends SFragment implements
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId, boolean enableSwipeToRefresh) {
TimelineFragment fragment = new TimelineFragment();
Bundle arguments = new Bundle();
Bundle arguments = new Bundle(3);
arguments.putString(KIND_ARG, kind.name());
arguments.putString(HASHTAG_OR_ID_ARG, hashtagOrId);
arguments.putString(ID_ARG, hashtagOrId);
arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh);
fragment.setArguments(arguments);
return fragment;
}
public static TimelineFragment newHashtagInstance(@NonNull List<String> hashtags) {
TimelineFragment fragment = new TimelineFragment();
Bundle arguments = new Bundle(3);
arguments.putString(KIND_ARG, Kind.TAG.name());
arguments.putStringArrayList(HASHTAGS_ARG, new ArrayList<>(hashtags));
arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true);
fragment.setArguments(arguments);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = requireArguments();
kind = Kind.valueOf(arguments.getString(KIND_ARG));
if (kind == Kind.TAG
|| kind == Kind.USER
if (kind == Kind.USER
|| kind == Kind.USER_PINNED
|| kind == Kind.USER_WITH_REPLIES
|| kind == Kind.LIST) {
hashtagOrId = arguments.getString(HASHTAG_OR_ID_ARG);
id = arguments.getString(ID_ARG);
}
if(kind == Kind.TAG) {
tags = arguments.getStringArrayList(HASHTAGS_ARG);
}
preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
@ -918,7 +933,7 @@ public class TimelineFragment extends SFragment implements
@Override
public void onViewTag(String tag) {
if (kind == Kind.TAG && hashtagOrId.equals(tag)) {
if (kind == Kind.TAG && tags.size() == 1 && tags.contains(tag)) {
// If already viewing a tag page, then ignore any request to view that tag again.
return;
}
@ -927,7 +942,7 @@ public class TimelineFragment extends SFragment implements
@Override
public void onViewAccount(String id) {
if ((kind == Kind.USER || kind == Kind.USER_WITH_REPLIES) && hashtagOrId.equals(id)) {
if ((kind == Kind.USER || kind == Kind.USER_WITH_REPLIES) && this.id.equals(id)) {
/* If already viewing an account page, then any requests to view that account page
* should be ignored. */
return;
@ -1088,8 +1103,7 @@ public class TimelineFragment extends SFragment implements
}
}
private Call<List<Status>> getFetchCallByTimelineType(Kind kind, String tagOrId, String fromId,
String uptoId) {
private Call<List<Status>> getFetchCallByTimelineType(String fromId, String uptoId) {
MastodonApi api = mastodonApi;
switch (kind) {
default:
@ -1100,19 +1114,21 @@ public class TimelineFragment extends SFragment implements
case PUBLIC_LOCAL:
return api.publicTimeline(true, fromId, uptoId, LOAD_AT_ONCE);
case TAG:
return api.hashtagTimeline(tagOrId, null, fromId, uptoId, LOAD_AT_ONCE);
String firstHashtag = tags.get(0);
List<String> additionalHashtags = tags.subList(1, tags.size());
return api.hashtagTimeline(firstHashtag, additionalHashtags, null, fromId, uptoId, LOAD_AT_ONCE);
case USER:
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, true, null, null);
return api.accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, true, null, null);
case USER_PINNED:
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null, null, true);
return api.accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, true);
case USER_WITH_REPLIES:
return api.accountStatuses(tagOrId, fromId, uptoId, LOAD_AT_ONCE, null, null, null);
return api.accountStatuses(id, fromId, uptoId, LOAD_AT_ONCE, null, null, null);
case FAVOURITES:
return api.favourites(fromId, uptoId, LOAD_AT_ONCE);
case BOOKMARKS:
return api.bookmarks(fromId, uptoId, LOAD_AT_ONCE);
case LIST:
return api.listTimeline(tagOrId, fromId, uptoId, LOAD_AT_ONCE);
return api.listTimeline(id, fromId, uptoId, LOAD_AT_ONCE);
}
}
@ -1154,7 +1170,7 @@ public class TimelineFragment extends SFragment implements
}
};
Call<List<Status>> listCall = getFetchCallByTimelineType(kind, hashtagOrId, maxId, sinceId);
Call<List<Status>> listCall = getFetchCallByTimelineType(maxId, sinceId);
callList.add(listCall);
listCall.enqueue(callback);
}
@ -1458,7 +1474,7 @@ public class TimelineFragment extends SFragment implements
break;
case USER:
case USER_WITH_REPLIES:
if (status.getAccount().getId().equals(hashtagOrId)) {
if (status.getAccount().getId().equals(id)) {
break;
} else {
return;

View File

@ -76,6 +76,7 @@ interface MastodonApi {
@GET("api/v1/timelines/tag/{hashtag}")
fun hashtagTimeline(
@Path("hashtag") hashtag: String,
@Query("any[]") any: List<String>?,
@Query("local") local: Boolean?,
@Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?,

View File

@ -1,146 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.util;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.style.ReplacementSpan;
import android.view.View;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.keylesspalace.tusky.entity.Emoji;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class CustomEmojiHelper {
/**
* replaces emoji shortcodes in a text with EmojiSpans
* @param text the text containing custom emojis
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances)
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable)
* @return the text with the shortcodes replaced by EmojiSpans
*/
@NonNull
public static Spanned emojifyText(@NonNull Spanned text, @Nullable List<Emoji> emojis, @NonNull final View view) {
if (emojis != null && !emojis.isEmpty()) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
for (Emoji emoji : emojis) {
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':');
Matcher matcher = Pattern.compile(pattern.toString()).matcher(text);
while (matcher.find()) {
EmojiSpan span = new EmojiSpan(view);
builder.setSpan(span, matcher.start(), matcher.end(), 0);
Glide.with(view)
.asBitmap()
.load(emoji.getUrl())
.into(span.getTarget());
}
}
return builder;
}
return text;
}
@NonNull
public static Spanned emojifyString(@NonNull String string, @Nullable List<Emoji> emojis, @NonNull final View ciew) {
return emojifyText(new SpannedString(string), emojis, ciew);
}
public static class EmojiSpan extends ReplacementSpan {
@Nullable
private Drawable imageDrawable;
private WeakReference<View> viewWeakReference;
EmojiSpan(View view) {
this.viewWeakReference = new WeakReference<>(view);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
@Nullable Paint.FontMetricsInt fm) {
/* update FontMetricsInt or otherwise span does not get drawn when
it covers the whole text */
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
if (fm != null) {
fm.top = metrics.top;
fm.ascent = metrics.ascent;
fm.descent = metrics.descent;
fm.bottom = metrics.bottom;
}
return (int) (paint.getTextSize()*1.2);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable == null) return;
canvas.save();
int emojiSize = (int) (paint.getTextSize() * 1.1);
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - imageDrawable.getBounds().bottom;
transY -= paint.getFontMetricsInt().descent/2;
canvas.translate(x, transY);
imageDrawable.draw(canvas);
canvas.restore();
}
Target<Bitmap> getTarget(){
return new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
View view = viewWeakReference.get();
if (view != null) {
imageDrawable = new BitmapDrawable(view.getContext().getResources(), resource);
view.invalidate();
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
//Do nothing on load cleared
}
};
}
}
}

View File

@ -0,0 +1,112 @@
/* Copyright 2020 Tusky Contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
@file:JvmName("CustomEmojiHelper")
package com.keylesspalace.tusky.util
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.SpannableStringBuilder
import android.text.style.ReplacementSpan
import android.view.View
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import com.keylesspalace.tusky.entity.Emoji
import java.lang.ref.WeakReference
import java.util.regex.Pattern
/**
* replaces emoji shortcodes in a text with EmojiSpans
* @param text the text containing custom emojis
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances)
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable)
* @return the text with the shortcodes replaced by EmojiSpans
*/
fun CharSequence.emojify(emojis: List<Emoji>?, view: View) : CharSequence {
if(emojis.isNullOrEmpty())
return this
val builder = SpannableStringBuilder.valueOf(this)
emojis.forEach { (shortcode, url) ->
val matcher = Pattern.compile(":$shortcode:", Pattern.LITERAL)
.matcher(this)
while(matcher.find()) {
val span = EmojiSpan(WeakReference(view))
builder.setSpan(span, matcher.start(), matcher.end(), 0);
Glide.with(view)
.asBitmap()
.load(url)
.into(span.getTarget())
}
}
return builder
}
class EmojiSpan(val viewWeakReference: WeakReference<View>) : ReplacementSpan() {
var imageDrawable: Drawable? = null
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?) : Int {
if (fm != null) {
/* update FontMetricsInt or otherwise span does not get drawn when
* it covers the whole text */
val metrics = paint.fontMetricsInt
fm.top = metrics.top
fm.ascent = metrics.ascent
fm.descent = metrics.descent
fm.bottom = metrics.bottom
}
return (paint.textSize * 1.2).toInt()
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
imageDrawable?.let { drawable ->
canvas.save()
val emojiSize = (paint.textSize * 1.1).toInt()
drawable.setBounds(0, 0, emojiSize, emojiSize)
var transY = bottom - drawable.bounds.bottom
transY -= paint.fontMetricsInt.descent / 2;
canvas.translate(x, transY.toFloat())
drawable.draw(canvas)
canvas.restore()
}
}
fun getTarget(): Target<Bitmap> {
return object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
viewWeakReference.get()?.let { view ->
imageDrawable = BitmapDrawable(view.context.resources, resource)
view.invalidate()
}
}
override fun onLoadCleared(placeholder: Drawable?) {}
}
}
}

View File

@ -68,11 +68,11 @@ public class LinkHelper {
* @param mentions any '@' mentions which are known to be in the content
* @param listener to notify about particular spans that are clicked
*/
public static void setClickableText(TextView view, Spanned content,
public static void setClickableText(TextView view, CharSequence content,
@Nullable Status.Mention[] mentions, final LinkListener listener,
boolean removeQuote) {
SpannableStringBuilder builder = new SpannableStringBuilder(content);
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
SpannableStringBuilder builder = SpannableStringBuilder.valueOf(content);
URLSpan[] urlSpans = builder.getSpans(0, content.length(), URLSpan.class);
for (URLSpan span : urlSpans) {
int start = builder.getSpanStart(span);
int end = builder.getSpanEnd(span);

View File

@ -302,7 +302,7 @@ class StatusViewHelper(private val itemView: View) {
val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount)
val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context)
pollResults[i].text = CustomEmojiHelper.emojifyText(pollOptionText, emojis, pollResults[i])
pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i])
pollResults[i].visibility = View.VISIBLE
val level = percent * 100

View File

@ -55,7 +55,7 @@ public class QuoteInlineHelper {
}
private void setDisplayName(String name, List<Emoji> customEmojis) {
CharSequence emojifiedName = CustomEmojiHelper.emojifyString(name, customEmojis, quoteDisplayName);
CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, quoteDisplayName);
quoteDisplayName.setText(emojifiedName);
}
@ -69,7 +69,7 @@ public class QuoteInlineHelper {
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);
CharSequence emojifiedText = CustomEmojiHelper.emojify(singleLineText, emojis, quoteContent);
LinkHelper.setClickableText(quoteContent, emojifiedText, mentions, listener, false);
}
@ -79,7 +79,7 @@ public class QuoteInlineHelper {
private void setSpoilerText(String spoilerText, List<Emoji> emojis) {
CharSequence emojiSpoiler =
CustomEmojiHelper.emojifyString(spoilerText, emojis, quoteContentWarningDescription);
CustomEmojiHelper.emojify(spoilerText, emojis, quoteContentWarningDescription);
quoteContentWarningDescription.setText(emojiSpoiler);
quoteContentWarningDescription.setVisibility(View.VISIBLE);
quoteContentWarningButton.setVisibility(View.VISIBLE);

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="24dp"
android:layout_height="24dp">
<background android:drawable="@color/tusky_blue" />
<foreground>
<inset
android:drawable="@drawable/ic_create_24dp"
android:inset="30%" />
</foreground>
</adaptive-icon>

View File

@ -1,9 +0,0 @@
<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="?android:attr/textColorPrimary"
android:pathData="m4.5088,16.3703v3.1209H7.6297L16.8342,10.2866 13.7134,7.1658ZM19.2477,7.8732c0.3246,-0.3246 0.3246,-0.8489 0,-1.1735l-1.9474,-1.9474c-0.3246,-0.3246 -0.8489,-0.3246 -1.1735,0l-1.523,1.523 3.1209,3.1209z" />
</vector>

View File

@ -26,10 +26,10 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_weight="1"
android:drawablePadding="12dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="?attr/status_text_large"
app:layout_constraintBottom_toTopOf="@id/chipGroup"
@ -57,9 +57,7 @@
android:id="@+id/chipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginBottom="8dp"
android:paddingTop="8dp"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.chip.Chip
@ -68,7 +66,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="false"
tools:text="add hashtag" />
android:text="@string/add_hashtag_title"
app:chipIcon="@drawable/ic_plus_24dp"
app:chipSurfaceColor="@color/tusky_blue" />
</com.google.android.material.chip.ChipGroup>

View File

@ -342,9 +342,7 @@
<string name="downloading_media">جارٍ تنزيل الوسائط</string>
<string name="dialog_redraft_toot_warning">هل تريد حذف وإعادة صياغة هذا التبويق؟</string>
<string name="description_status_favourited">تم تفضيله</string>
<string name="edit_hashtag_title">تعديل الوسم</string>
<string name="edit_hashtag_hint">وسم بدون #</string>
<string name="hashtag">الوسم</string>
<string name="notifications_clear">مسح</string>
<string name="notifications_apply_filter">عامل تصفية</string>
<string name="filter_apply">طَبِّق</string>

View File

@ -416,9 +416,7 @@
<string name="hint_list_name">নামের তালিকা</string>
<string name="edit_hashtag_title">হ্যাশট্যাগ সম্পাদনা করুন</string>
<string name="edit_hashtag_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="hashtag">হ্যাশট্যাগ</string>
<string name="notifications_clear">পরিষ্কার</string>
<string name="notifications_apply_filter">ফিল্টার</string>
<string name="filter_apply">প্রয়োগ</string>

View File

@ -420,9 +420,7 @@
<string name="description_visiblity_direct">Directe</string>
<string name="hint_list_name">Nom de la llista</string>
<string name="edit_hashtag_title">Modificar el hashtag</string>
<string name="edit_hashtag_hint">Hashtag sense #</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Netejar</string>
<string name="notifications_apply_filter">Filtrar</string>
<string name="filter_apply">Aplicar</string>

View File

@ -364,9 +364,7 @@
<string name="description_visiblity_direct"> Přímý
</string>
<string name="hint_list_name">Název seznamu</string>
<string name="edit_hashtag_title">Upravit hashtag</string>
<string name="edit_hashtag_hint">Hashtag bez #</string>
<string name="hashtag">Hashtag</string>
<string name="compose_shortcut_long_label">Napsat toot</string>
<string name="compose_shortcut_short_label">Napsat</string>

View File

@ -329,9 +329,7 @@
<string name="error_delete_list">Liste konnte nicht gelöscht werden</string>
<string name="hint_search_people_list">Suche nach Leuten denen du folgst</string>
<string name="action_remove_from_list">Von der Liste entfernen</string>
<string name="edit_hashtag_title">Hashtag bearbeiten</string>
<string name="edit_hashtag_hint">Hashtag ohne #</string>
<string name="hashtag">Hashtag</string>
<string name="action_open_reblogger">Öffne Autor des geteilten Beitrages</string>
<string name="pref_title_public_filter_keywords">Öffentliche Zeitleisten</string>
<plurals name="favs">

View File

@ -367,10 +367,7 @@
<string name="notification_poll_name">Enketoj</string>
<string name="notification_poll_description">Sciigoj pri enketoj kiuj finiĝis</string>
<string name="edit_hashtag_title">Redakti kradvorton</string>
<string name="edit_hashtag_hint">Kradvortoj sen #</string>
<string name="hashtag">Kradvorto</string>
<string name="notifications_clear">Viŝi</string>
<string name="notifications_apply_filter">Filtri</string>
<string name="filter_apply">Apliki</string>

View File

@ -381,9 +381,7 @@
<string name="description_visiblity_unlisted">Sin listar</string>
<string name="description_visiblity_direct">Directo</string>
<string name="hint_list_name">Nombre de la lista</string>
<string name="edit_hashtag_title">Editar etiqueta</string>
<string name="edit_hashtag_hint">Etiqueta sin #</string>
<string name="hashtag">Etiqueta</string>
<string name="notifications_clear">Limpiar</string>
<string name="notifications_apply_filter">Filtro</string>
<string name="compose_shortcut_long_label">Componer toot</string>

View File

@ -373,9 +373,7 @@
<string name="description_visiblity_direct">Zuzena</string>
<string name="description_poll">Inkestatu aukerekin: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Zerrendaren izena</string>
<string name="edit_hashtag_title">Editatu traola</string>
<string name="edit_hashtag_hint">Traola # gabe</string>
<string name="hashtag">Traola</string>
<string name="notifications_clear">Garbitu</string>
<string name="notifications_apply_filter">Iragazi</string>
<string name="filter_apply">Aplikatu</string>

View File

@ -364,9 +364,7 @@
<string name="description_visiblity_direct">مستقیم</string>
<string name="description_poll">نظرسنجی با انتخاب‌ها: %1$s، %2$s، %3$s، %4$s؛ %5$s</string>
<string name="hint_list_name">نام فهرست</string>
<string name="edit_hashtag_title">ویرایش برچسب</string>
<string name="edit_hashtag_hint">برچسب بدون #</string>
<string name="hashtag">برچسب</string>
<string name="notifications_clear">پاک‌سازی</string>
<string name="notifications_apply_filter">پالایش</string>
<string name="filter_apply">اعمال</string>

View File

@ -362,9 +362,7 @@
<string name="description_visiblity_direct"> Direct
</string>
<string name="hint_list_name">Nom de la liste</string>
<string name="edit_hashtag_title">Édition des hashtags</string>
<string name="edit_hashtag_hint">Hashtags sans #</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Effacer</string>
<string name="notifications_apply_filter">Filtrer</string>
<string name="filter_apply">Appliquer</string>

View File

@ -390,9 +390,7 @@
<string name="hint_list_name">Lista neve</string>
<string name="edit_hashtag_title">Hashtag szerkesztése</string>
<string name="edit_hashtag_hint">Hashtag # nélkül</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Törlés</string>
<string name="notifications_apply_filter">Szűrés</string>
<string name="filter_apply">Alkalmaz</string>

View File

@ -428,9 +428,7 @@
<string name="hint_list_name">Heiti á lista</string>
<string name="edit_hashtag_title">Breyta myllumerki</string>
<string name="edit_hashtag_hint">Myllumerki án #</string>
<string name="hashtag">Myllumerki</string>
<string name="select_list_title">Veldu lista</string>
<string name="list">Listi</string>
<string name="notifications_clear">Hreinsa</string>

View File

@ -359,9 +359,7 @@
<string name="download_media">Scarica media</string>
<string name="downloading_media">Scaricando media</string>
<string name="edit_hashtag_title">Modifica hashtag</string>
<string name="edit_hashtag_hint">Hashtag senza #</string>
<string name="hashtag">Hashtag</string>
<string name="compose_shortcut_long_label">Componi Toot</string>

View File

@ -409,8 +409,6 @@
<string name="pref_title_public_filter_keywords">公開タイムライン</string>
<string name="description_status_cw">閲覧注意:%s</string>
<string name="edit_hashtag_title">ハッシュタグの編集</string>
<string name="hashtag">ハッシュタグ</string>
<string name="filter_apply">適用</string>
<string name="poll_info_closed">投票終了</string>

View File

@ -368,9 +368,7 @@
<string name="description_visiblity_direct">다이렉트</string>
<string name="description_poll">투표 선택지: %1$s, %2$s, %3$s, %4$s, %5$s</string>
<string name="hint_list_name">리스트 이름</string>
<string name="edit_hashtag_title">해시태그 편집</string>
<string name="edit_hashtag_hint">#를 제외한 해시태그</string>
<string name="hashtag">해시태그</string>
<string name="notifications_clear">알림 지우기</string>
<string name="notifications_apply_filter">필터</string>
<string name="filter_apply">적용</string>

View File

@ -356,9 +356,7 @@
<string name="action_add_to_list">Account aan de lijst toevoegen</string>
<string name="action_remove_from_list">Account uit de lijst verwijderen</string>
<string name="hint_list_name">Naam van lijst</string>
<string name="edit_hashtag_title">Hashtag bewerken</string>
<string name="edit_hashtag_hint">Hashtag zonder #</string>
<string name="hashtag">Hashtag</string>
<string name="action_delete_and_redraft">Verwijderen en herschrijven</string>
<string name="dialog_redraft_toot_warning">Deze toot verwijderen en herschrijven\?</string>
<string name="notifications_clear">Leegmaken</string>

View File

@ -328,9 +328,7 @@
<string name="description_visiblity_private">Følgere</string>
<string name="description_visiblity_direct">Direkte</string>
<string name="hint_list_name">Listenavn</string>
<string name="edit_hashtag_title">Endre emneord</string>
<string name="edit_hashtag_hint">Emneord uten #</string>
<string name="hashtag">Emneord</string>
<string name="notifications_clear">Slett</string>
<string name="notifications_apply_filter">Filter</string>
<string name="filter_apply">Bruk</string>

View File

@ -351,9 +351,7 @@
<string name="description_visiblity_private">Seguidors</string>
<string name="description_visiblity_direct">Dirècte</string>
<string name="hint_list_name">Nom de la lista</string>
<string name="edit_hashtag_title">Modificar las etiquetas</string>
<string name="edit_hashtag_hint">Etiquetas sens #</string>
<string name="hashtag">Etiqueta</string>
<string name="compose_shortcut_long_label">Escriure un tut</string>
<string name="compose_shortcut_short_label">Escriure</string>
<string name="action_delete_and_redraft">Suprimir e reformular</string>

View File

@ -373,9 +373,7 @@
<string name="description_visiblity_direct">Bezpośrednio</string>
<string name="description_poll">Głosowanie z opcjami: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Nazwa listy</string>
<string name="edit_hashtag_title">Edytuj hashtag</string>
<string name="edit_hashtag_hint">Hashtag bez #</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Wyczyść</string>
<string name="notifications_apply_filter">Filtr</string>
<string name="filter_apply">Zastosuj</string>

View File

@ -356,9 +356,7 @@
<string name="description_visiblity_unlisted">Não-listado</string>
<string name="description_visiblity_direct">Direta</string>
<string name="hint_list_name">Nome da lista</string>
<string name="edit_hashtag_title">Editar hashtag</string>
<string name="edit_hashtag_hint">Hashtag sem #</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Limpar</string>
<string name="notifications_apply_filter">Filtro</string>
<string name="filter_apply">Salvar</string>

View File

@ -394,9 +394,7 @@
Для упомянутых
</string>
<string name="hint_list_name">Название списка</string>
<string name="edit_hashtag_title">Изм. хэштег</string>
<string name="edit_hashtag_hint">Хэштег без #</string>
<string name="hashtag">Хэштег</string>
<string name="notifications_clear">Очистить</string>
<string name="notifications_apply_filter">Фильтр</string>
<string name="filter_apply">Применить</string>

View File

@ -332,9 +332,7 @@
<string name="description_visiblity_private">Sledilci</string>
<string name="description_visiblity_direct">Neposredno</string>
<string name="hint_list_name">Ime seznama</string>
<string name="edit_hashtag_title">Uredi ključnik</string>
<string name="edit_hashtag_hint">Ključnik brez #</string>
<string name="hashtag">Ključnik</string>
<string name="notifications_clear">Počisti</string>
<string name="notifications_apply_filter">Filter</string>
<string name="filter_apply">Uporabi</string>

View File

@ -358,9 +358,7 @@
<string name="hint_list_name">Listnamn</string>
<string name="download_media">Ladda ned media</string>
<string name="downloading_media">Laddar ned media</string>
<string name="edit_hashtag_title">Redigera hashtag</string>
<string name="edit_hashtag_hint">Hashtag utan #</string>
<string name="hashtag">Hashtag</string>
<string name="compose_shortcut_long_label">Skriv toot</string>
<string name="compose_shortcut_short_label">Skriv</string>
<string name="notifications_clear">Rensa</string>

View File

@ -351,9 +351,7 @@
<string name="description_visiblity_direct">Direkt</string>
<string name="description_poll">Seçenekli anket: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Liste adı</string>
<string name="edit_hashtag_title">Hashtag\'i düzenle</string>
<string name="edit_hashtag_hint"># olmadan hashtag</string>
<string name="hashtag">Hashtag</string>
<string name="notifications_clear">Temizle</string>
<string name="notifications_apply_filter">Filtre</string>
<string name="filter_apply">Uygula</string>

View File

@ -370,9 +370,7 @@
私信
</string>
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_title">编辑话题</string>
<string name="edit_hashtag_hint">话题名(不含前面的 # 号)</string>
<string name="hashtag">话题</string>
<string name="notifications_clear">清空</string>
<string name="notifications_apply_filter">分类</string>
<string name="filter_apply">应用</string>

View File

@ -366,9 +366,7 @@
私信
</string>
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_title">編輯話題</string>
<string name="edit_hashtag_hint">話題名(不含前面的 # 號)</string>
<string name="hashtag">話題</string>
<string name="notifications_clear">清空</string>
<string name="notifications_apply_filter">分類</string>
<string name="filter_apply">應用</string>

View File

@ -366,9 +366,7 @@
私信
</string>
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_title">編輯話題</string>
<string name="edit_hashtag_hint">話題名(不含前面的 # 號)</string>
<string name="hashtag">話題</string>
<string name="notifications_clear">清空</string>
<string name="notifications_apply_filter">分類</string>
<string name="filter_apply">應用</string>

View File

@ -434,9 +434,7 @@
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_title">编辑话题</string>
<string name="edit_hashtag_hint">话题名(不含前面的 # 号)</string>
<string name="hashtag">话题</string>
<string name="notifications_clear">清空</string>
<string name="notifications_apply_filter">分类</string>
<string name="filter_apply">应用</string>

View File

@ -366,9 +366,7 @@
私信
</string>
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_title">編輯話題</string>
<string name="edit_hashtag_hint">話題名(不含前面的 # 號)</string>
<string name="hashtag">話題</string>
<string name="notifications_clear">清空</string>
<string name="notifications_apply_filter">分類</string>
<string name="filter_apply">應用</string>

View File

@ -503,9 +503,9 @@
<string name="hint_list_name">List name</string>
<string name="edit_hashtag_title">Edit hashtag</string>
<string name="add_hashtag_title">Add hashtag</string>
<string name="edit_hashtag_hint">Hashtag without #</string>
<string name="hashtag">Hashtag</string>
<string name="hashtags">Hashtags</string>
<string name="select_list_title">Select list</string>
<string name="list">List</string>
<string name="notifications_clear">Clear</string>