merge hashtag dialogs into one (#4861)

A hashtag picker dialog was implemented twice, with slight differences.
Now there is only one
- with hashtag validation - no more api errors when following an invalid
one
- The dialog can now be closed with the keyboard, for extra fast hashtag
selection
- with autocomplete

I also added a new snackbar when following a hashtag was succesfull.

Although I'm not sure about the auto complete, it can be very annoying
as the drop down covers the buttons. I found no way to make it size to
its content: https://chaos.social/@ConnyDuck/113803457147888844

Should we get rid of it?
This commit is contained in:
Konrad Pozniak 2025-01-14 19:29:07 +01:00 committed by GitHub
commit 9735683df4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 233 additions and 210 deletions

View file

@ -18,17 +18,13 @@ package com.keylesspalace.tusky
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialArcMotion
import com.google.android.material.transition.MaterialContainerTransform
import com.keylesspalace.tusky.adapter.ItemInteractionListener
@ -37,13 +33,13 @@ import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
import com.keylesspalace.tusky.components.account.list.ListSelectionFragment
import com.keylesspalace.tusky.databinding.ActivityTabPreferenceBinding
import com.keylesspalace.tusky.databinding.DialogAddHashtagBinding
import com.keylesspalace.tusky.entity.MastoList
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.hashtagPattern
import com.keylesspalace.tusky.util.unsafeLazy
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.view.showHashtagPickerDialog
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@ -224,39 +220,21 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener, ListSelec
}
private fun showAddHashtagDialog(tab: TabData? = null, tabPosition: Int = 0) {
val dialogBinding = DialogAddHashtagBinding.inflate(layoutInflater)
val editText = dialogBinding.addHashtagEditText
showHashtagPickerDialog(mastodonApi, R.string.add_hashtag_title) { hashtag ->
if (tab == null) {
val newTab = createTabDataFromId(HASHTAG, listOf(hashtag))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
} else {
val newTab = tab.copy(arguments = tab.arguments + hashtag)
currentTabs[tabPosition] = newTab
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.add_hashtag_title)
.setView(dialogBinding.root)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_save) { _, _ ->
val input = editText.text.toString().trim()
if (tab == null) {
val newTab = createTabDataFromId(HASHTAG, listOf(input))
currentTabs.add(newTab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
} else {
val newTab = tab.copy(arguments = tab.arguments + input)
currentTabs[tabPosition] = newTab
currentTabsAdapter.notifyItemChanged(tabPosition)
}
updateAvailableTabs()
saveTabs()
currentTabsAdapter.notifyItemChanged(tabPosition)
}
.create()
editText.doOnTextChanged { s, _, _, _ ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(s)
updateAvailableTabs()
saveTabs()
}
dialog.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(editText.text)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
editText.requestFocus()
}
private var listSelectDialog: ListSelectionFragment? = null

View file

@ -464,9 +464,9 @@ class ComposeActivity :
binding.composeEditField.setAdapter(
ComposeAutoCompleteAdapter(
this,
preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
animateAvatar = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showBotBadge = preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true)
)
)
binding.composeEditField.setTokenizer(ComposeTokenizer())

View file

@ -37,7 +37,9 @@ class ComposeAutoCompleteAdapter(
private val autocompletionProvider: AutocompletionProvider,
private val animateAvatar: Boolean,
private val animateEmojis: Boolean,
private val showBotBadge: Boolean
private val showBotBadge: Boolean,
// if true, @ # : are returned in the result, otherwise only the raw value
private val withDecoration: Boolean = true,
) : BaseAdapter(), Filterable {
private var resultList: List<AutocompleteResult> = emptyList()
@ -52,37 +54,35 @@ class ComposeAutoCompleteAdapter(
return position.toLong()
}
override fun getFilter(): Filter {
return object : Filter() {
override fun getFilter() = object : Filter() {
override fun convertResultToString(resultValue: Any): CharSequence {
return when (resultValue) {
is AutocompleteResult.AccountResult -> "@${resultValue.account.username}"
is AutocompleteResult.HashtagResult -> "#${resultValue.hashtag}"
is AutocompleteResult.EmojiResult -> ":${resultValue.emoji.shortcode}:"
else -> ""
}
override fun convertResultToString(resultValue: Any): CharSequence {
return when (resultValue) {
is AutocompleteResult.AccountResult -> if (withDecoration) "@${resultValue.account.username}" else resultValue.account.username
is AutocompleteResult.HashtagResult -> if (withDecoration) "#${resultValue.hashtag}" else resultValue.hashtag
is AutocompleteResult.EmojiResult -> if (withDecoration) ":${resultValue.emoji.shortcode}:" else resultValue.emoji.shortcode
else -> ""
}
}
@WorkerThread
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (constraint != null) {
val results = autocompletionProvider.search(constraint.toString())
filterResults.values = results
filterResults.count = results.size
}
return filterResults
@WorkerThread
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (constraint != null) {
val results = autocompletionProvider.search(constraint.toString())
filterResults.values = results
filterResults.count = results.size
}
return filterResults
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
if (results.count > 0) {
resultList = results.values as List<AutocompleteResult>
notifyDataSetChanged()
} else {
notifyDataSetInvalidated()
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
if (results.count > 0) {
resultList = results.values as List<AutocompleteResult>
notifyDataSetChanged()
} else {
notifyDataSetInvalidated()
}
}
}

View file

@ -3,7 +3,6 @@ package com.keylesspalace.tusky.components.followedtags
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
@ -11,14 +10,11 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.StatusListActivity
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.databinding.ActivityFollowedTagsBinding
import com.keylesspalace.tusky.databinding.DialogFollowHashtagBinding
import com.keylesspalace.tusky.interfaces.HashtagActionListener
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.copyToClipboard
@ -26,6 +22,7 @@ import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.view.showHashtagPickerDialog
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest
@ -34,8 +31,8 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint
class FollowedTagsActivity :
BaseActivity(),
HashtagActionListener,
ComposeAutoCompleteAdapter.AutocompletionProvider {
HashtagActionListener {
@Inject
lateinit var api: MastodonApi
@ -105,7 +102,7 @@ class FollowedTagsActivity :
private fun follow(tagName: String, position: Int = -1) {
lifecycleScope.launch {
api.followTag(tagName).fold(
val snackbarText = api.followTag(tagName).fold(
{
if (position == -1) {
viewModel.tags.add(it)
@ -113,17 +110,20 @@ class FollowedTagsActivity :
viewModel.tags.add(position, it)
}
viewModel.currentSource?.invalidate()
getString(R.string.follow_hashtag_success, tagName)
},
{
Snackbar.make(
this@FollowedTagsActivity,
binding.followedTagsView,
getString(R.string.error_following_hashtag_format, tagName),
Snackbar.LENGTH_SHORT
)
.show()
{ t ->
Log.w(TAG, "failed to follow hashtag $tagName", t)
getString(R.string.error_following_hashtag_format, tagName)
}
)
Snackbar.make(
this@FollowedTagsActivity,
binding.followedTagsView,
snackbarText,
Snackbar.LENGTH_SHORT
)
.show()
}
}
@ -160,10 +160,6 @@ class FollowedTagsActivity :
}
}
override fun search(token: String): List<ComposeAutoCompleteAdapter.AutocompleteResult> {
return viewModel.searchAutocompleteSuggestions(token)
}
override fun viewTag(tagName: String) {
startActivity(StatusListActivity.newHashtagIntent(this, tagName))
}
@ -176,30 +172,9 @@ class FollowedTagsActivity :
}
private fun showDialog() {
val dialogBinding = DialogFollowHashtagBinding.inflate(layoutInflater)
dialogBinding.hashtagAutoCompleteTextView.setAdapter(
ComposeAutoCompleteAdapter(
this,
animateAvatar = false,
animateEmojis = false,
showBotBadge = false
)
)
dialogBinding.hashtagAutoCompleteTextView.requestFocus()
dialogBinding.hashtagAutoCompleteTextView.setSelection(dialogBinding.hashtagAutoCompleteTextView.length())
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.dialog_follow_hashtag_title)
.setView(dialogBinding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
follow(
dialogBinding.hashtagAutoCompleteTextView.text.toString().removePrefix("#")
)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show()
showHashtagPickerDialog(api, R.string.dialog_follow_hashtag_title) { hashtag ->
follow(hashtag)
}
}
companion object {

View file

@ -1,24 +1,19 @@
package com.keylesspalace.tusky.components.followedtags
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import at.connyduck.calladapter.networkresult.fold
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.components.search.SearchType
import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.network.MastodonApi
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
@HiltViewModel
class FollowedTagsViewModel @Inject constructor(
private val api: MastodonApi
val api: MastodonApi
) : ViewModel() {
val tags: MutableList<HashTag> = mutableListOf()
var nextKey: String? = null
@ -39,24 +34,6 @@ class FollowedTagsViewModel @Inject constructor(
}
).flow.cachedIn(viewModelScope)
fun searchAutocompleteSuggestions(
token: String
): List<ComposeAutoCompleteAdapter.AutocompleteResult> {
return runBlocking {
api.search(query = token, type = SearchType.Hashtag.apiParameter, limit = 10)
.fold({ searchResult ->
searchResult.hashtags.map {
ComposeAutoCompleteAdapter.AutocompleteResult.HashtagResult(
it.name
)
}
}, { e ->
Log.e(TAG, "Autocomplete search for $token failed.", e)
emptyList()
})
}
}
companion object {
private const val TAG = "FollowedTagsViewModel"
}

View file

@ -0,0 +1,118 @@
/* Copyright 2025 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("HashTagPickerDialog")
package com.keylesspalace.tusky.view
import android.content.Context
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.components.search.SearchType
import com.keylesspalace.tusky.databinding.DialogPickHashtagBinding
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.hashtagPattern
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
fun Context.showHashtagPickerDialog(
api: MastodonApi,
@StringRes title: Int,
onHashtagSelected: (String) -> Unit
) {
val dialogScope = CoroutineScope(Dispatchers.Main)
val dialogBinding = DialogPickHashtagBinding.inflate(LayoutInflater.from(this))
val autocompleteTextView = dialogBinding.pickHashtagEditText
val autoCompleteProvider = object : ComposeAutoCompleteAdapter.AutocompletionProvider {
override fun search(token: String): List<ComposeAutoCompleteAdapter.AutocompleteResult> {
return runBlocking {
api.search(query = token, type = SearchType.Hashtag.apiParameter, limit = 5)
.fold({ searchResult ->
searchResult.hashtags.map {
ComposeAutoCompleteAdapter.AutocompleteResult.HashtagResult(
it.name
)
}
}, { e ->
Log.e("HashtagPickerDialog", "Autocomplete search for $token failed", e)
emptyList()
})
}
}
}
autocompleteTextView.setAdapter(
ComposeAutoCompleteAdapter(
autoCompleteProvider,
animateAvatar = false,
animateEmojis = false,
showBotBadge = false,
withDecoration = false
)
)
autocompleteTextView.setSelection(autocompleteTextView.length())
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(title)
.setView(dialogBinding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
onHashtagSelected(autocompleteTextView.text.toString())
}
.setNegativeButton(android.R.string.cancel, null)
.setOnDismissListener {
dialogScope.cancel()
}
.create()
autocompleteTextView.doOnTextChanged { s, _, _, _ ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(s)
}
autocompleteTextView.setOnEditorActionListener(object : OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == EditorInfo.IME_ACTION_DONE && validateHashtag(autocompleteTextView.text)) {
onHashtagSelected(autocompleteTextView.text.toString())
dialog.dismiss()
return true
}
return false
}
})
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(autocompleteTextView.text)
autocompleteTextView.requestFocus()
}
private fun validateHashtag(input: CharSequence?): Boolean {
val trimmedInput = input?.trim() ?: ""
return trimmedInput.isNotEmpty() && hashtagPattern.matcher(trimmedInput).matches()
}

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:paddingTop="16dp"
android:paddingRight="20dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/addHashtagInputLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edit_hashtag_hint"
app:endIconMode="none">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addHashtagEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions"
android:lines="1" />
</com.google.android.material.textfield.TextInputLayout>
</FrameLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -8,21 +8,23 @@
android:paddingRight="20dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/hashtagAutoCompleteInputLayout"
android:id="@+id/pickHashtagInputLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_follow_hashtag_hint"
android:hint="@string/pick_hashtag_hint"
app:endIconMode="none">
<!-- textNoSuggestions is to disable spell check, it will auto-complete -->
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/hashtagAutoCompleteTextView"
android:id="@+id/pickHashtagEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionThreshold="2"
android:imeOptions="actionDone"
android:inputType="textNoSuggestions"
android:lines="1" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</FrameLayout>

View file

@ -2,7 +2,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/hashtag"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"

View file

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

View file

@ -453,7 +453,7 @@
<string name="add_hashtag_title">Дадаць хэштэг</string>
<string name="description_post_reblogged">Пашырана</string>
<string name="description_poll">Апытанне з варыянтамі: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="edit_hashtag_hint">Хэштэг без #</string>
<string name="pick_hashtag_hint">Хэштэг без #</string>
<string name="description_post_edited">Зменена</string>
<string name="description_post_bookmarked">Дададзена ў закладкі</string>
<string name="description_post_favourited">Упадабана</string>

View file

@ -61,7 +61,7 @@
<string name="list">Списък</string>
<string name="select_list_title">Избиране на списък</string>
<string name="hashtags">Хаштагове</string>
<string name="edit_hashtag_hint">Хаштаг без #</string>
<string name="pick_hashtag_hint">Хаштаг без #</string>
<string name="add_hashtag_title">Добавяне на хаштаг</string>
<string name="hint_list_name">Име на списък</string>
<string name="description_poll">Анкета с избори: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -383,7 +383,7 @@
<string name="filter_apply">প্রয়োগ</string>
<string name="notifications_apply_filter">ফিল্টার</string>
<string name="notifications_clear">পরিষ্কার</string>
<string name="edit_hashtag_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="pick_hashtag_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="hint_list_name">নামের তালিকা</string>
<string name="title_favourites">প্রিয়গুলো</string>
<string name="title_followers">অনুগামিবৃন্দ</string>

View file

@ -351,7 +351,7 @@
<string name="description_visibility_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_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="pick_hashtag_hint"># ছাড়া হ্যাশট্যাগ</string>
<string name="notifications_clear">পরিষ্কার</string>
<string name="notifications_apply_filter">ফিল্টার</string>
<string name="filter_apply">প্রয়োগ</string>

View file

@ -347,7 +347,7 @@
<string name="description_visibility_private">Seguidors</string>
<string name="description_visibility_direct">Directe</string>
<string name="hint_list_name">Nom de la llista</string>
<string name="edit_hashtag_hint">Hashtag sense #</string>
<string name="pick_hashtag_hint">Hashtag sense #</string>
<string name="notifications_clear">Netejar</string>
<string name="notifications_apply_filter">Filtrar</string>
<string name="filter_apply">Aplicar</string>

View file

@ -306,7 +306,7 @@
<string name="list">لیست</string>
<string name="select_list_title">دیاریکردنی لیست</string>
<string name="hashtags">هاشتاگی</string>
<string name="edit_hashtag_hint">هاشتاگی بێ #</string>
<string name="pick_hashtag_hint">هاشتاگی بێ #</string>
<string name="add_hashtag_title">هاشتاگی زیاد بکە</string>
<string name="hint_list_name">ناوی لیست</string>
<string name="description_poll">ڕاپرسی لەگەڵ هەڵبژاردنەکان: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -351,7 +351,7 @@
<string name="description_visibility_private">Pro sledující</string>
<string name="description_visibility_direct">Přímý</string>
<string name="hint_list_name">Název seznamu</string>
<string name="edit_hashtag_hint">Hashtag bez #</string>
<string name="pick_hashtag_hint">Hashtag bez #</string>
<string name="compose_shortcut_long_label">Napsat příspěvek</string>
<string name="compose_shortcut_short_label">Napsat</string>
<string name="notifications_clear">Vyčistit</string>

View file

@ -379,7 +379,7 @@
<string name="delete_scheduled_post_warning">Dileu\'r neges wedi\'u hamserlennu hon\?</string>
<string name="description_post_media_no_description_placeholder">Dim disgrifiad</string>
<string name="hint_list_name">Enw\'r rhestr</string>
<string name="edit_hashtag_hint">Hashnod heb #</string>
<string name="pick_hashtag_hint">Hashnod heb #</string>
<string name="report_remote_instance">Anfon ymlaen at %1$s</string>
<string name="report_description_remote_instance">Daw\'r cyfrif o weinydd arall. Ydych chi am anfon copi dienw o\'r adroddiad i\'r gweinydd hwnnw hefyd\?</string>
<string name="duration_1_hour">Awr</string>

View file

@ -319,7 +319,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">Konto aus der Liste entfernen</string>
<string name="edit_hashtag_hint">Hashtag ohne #</string>
<string name="pick_hashtag_hint">Hashtag ohne #</string>
<string name="action_open_reblogger">Autor*in des geteilten Beitrags öffnen</string>
<string name="pref_title_public_filter_keywords">Öffentliche Timelines</string>
<plurals name="favs">

View file

@ -360,7 +360,7 @@
<string name="pref_title_animate_gif_avatars">Ebligi GIF-profilbildojn</string>
<string name="notification_poll_name">Enketoj</string>
<string name="notification_poll_description">Sciigoj pri enketoj, kiuj finiĝis</string>
<string name="edit_hashtag_hint">Kradvorto sen #</string>
<string name="pick_hashtag_hint">Kradvorto sen #</string>
<string name="notifications_clear">Viŝi</string>
<string name="notifications_apply_filter">Filtri</string>
<string name="filter_apply">Apliki</string>

View file

@ -382,7 +382,7 @@
<string name="description_visibility_unlisted">Sin listar</string>
<string name="description_visibility_direct">Directo</string>
<string name="hint_list_name">Nombre de la lista</string>
<string name="edit_hashtag_hint">Etiqueta sin #</string>
<string name="pick_hashtag_hint">Etiqueta sin #</string>
<string name="notifications_clear">Limpiar</string>
<string name="notifications_apply_filter">Filtro</string>
<string name="compose_shortcut_long_label">Escribir publicación</string>

View file

@ -367,7 +367,7 @@
<string name="description_visibility_direct">Zuzena</string>
<string name="description_poll">Inkesta aukerekin: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Zerrendaren izena</string>
<string name="edit_hashtag_hint">Traola # gabe</string>
<string name="pick_hashtag_hint">Traola # gabe</string>
<string name="notifications_clear">Garbitu</string>
<string name="notifications_apply_filter">Iragazi</string>
<string name="filter_apply">Aplikatu</string>

View file

@ -357,7 +357,7 @@
<string name="description_visibility_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_hint">برچسب بدون #</string>
<string name="pick_hashtag_hint">برچسب بدون #</string>
<string name="notifications_clear">حذف</string>
<string name="notifications_apply_filter">پالایش</string>
<string name="filter_apply">اعمال</string>

View file

@ -217,7 +217,7 @@
\n(enintään %1$d merkkiä)</item>
<item quantity="other"/>
</plurals>
<string name="edit_hashtag_hint">Aihetunniste ilman #-merkkiä</string>
<string name="pick_hashtag_hint">Aihetunniste ilman #-merkkiä</string>
<string name="title_domain_mutes">Piilotetus verkkonimet</string>
<string name="action_share_as">Jaa…</string>
<string name="error_no_web_browser_found">Verkkoselainta ei löytynyt.</string>

View file

@ -350,7 +350,7 @@
</string>
<string name="description_visibility_direct">Direct</string>
<string name="hint_list_name">Nom de la liste</string>
<string name="edit_hashtag_hint">Hashtag sans #</string>
<string name="pick_hashtag_hint">Hashtag sans #</string>
<string name="notifications_clear">Supprimer</string>
<string name="notifications_apply_filter">Filtrer</string>
<string name="filter_apply">Appliquer</string>

View file

@ -376,7 +376,7 @@
<string name="description_visibility_direct">Díreach</string>
<string name="hint_list_name">Ainm liosta</string>
<string name="add_hashtag_title">Cuir hashtag leis</string>
<string name="edit_hashtag_hint">Hashtag gan #</string>
<string name="pick_hashtag_hint">Hashtag gan #</string>
<string name="select_list_title">Roghnaigh liosta</string>
<string name="notifications_clear">Glan</string>
<string name="notifications_apply_filter">Scagaire</string>

View file

@ -171,7 +171,7 @@
<string name="notifications_clear">Sguab às</string>
<string name="list">Liosta</string>
<string name="select_list_title">Tagh liosta</string>
<string name="edit_hashtag_hint">Taga hais gun #</string>
<string name="pick_hashtag_hint">Taga hais gun #</string>
<string name="add_hashtag_title">Cuir taga hais ris</string>
<string name="hint_list_name">Ainm na liosta</string>
<string name="description_poll">Cunntas-bheachd le roghainnean: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -180,7 +180,7 @@
<string name="list">Lista</string>
<string name="select_list_title">Escoller lista</string>
<string name="hashtags">Cancelos</string>
<string name="edit_hashtag_hint">Cancelo sen #</string>
<string name="pick_hashtag_hint">Cancelo sen #</string>
<string name="add_hashtag_title">Engadir cancelo</string>
<string name="hint_list_name">Nome da lista</string>
<string name="description_poll">Enquisa con opcións: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -359,7 +359,7 @@
<string name="description_visibility_direct">Közvetlen</string>
<string name="description_poll">Szavazás válaszokkal: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Lista neve</string>
<string name="edit_hashtag_hint">Hashtag # nélkül</string>
<string name="pick_hashtag_hint">Hashtag # nélkül</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

@ -356,7 +356,7 @@
<string name="description_visibility_direct">Beint</string>
<string name="description_poll">Könnun með valkostunum: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Heiti á lista</string>
<string name="edit_hashtag_hint">Myllumerki án #</string>
<string name="pick_hashtag_hint">Myllumerki án #</string>
<string name="select_list_title">Veldu lista</string>
<string name="list">Listi</string>
<string name="notifications_clear">Eyða</string>

View file

@ -351,7 +351,7 @@
<string name="download_media">Scarica media</string>
<string name="downloading_media">Scaricando media</string>
<string name="compose_shortcut_long_label">Componi post</string>
<string name="edit_hashtag_hint">Hashtag senza #</string>
<string name="pick_hashtag_hint">Hashtag senza #</string>
<string name="compose_shortcut_short_label">Componi</string>
<string name="notifications_clear">Cancella</string>
<string name="notifications_apply_filter">Filtra</string>

View file

@ -432,7 +432,7 @@
<string name="pref_title_enable_swipe_for_tabs">タブ間の切り替えにスワイプのジェスチャーを有効化</string>
<string name="pref_title_show_cards_in_timelines">リンクのプレビューをタイムラインに表示</string>
<string name="pref_title_confirm_reblogs">ブーストする前に確認を表示</string>
<string name="edit_hashtag_hint">ハッシュタグ (#をつけない)</string>
<string name="pick_hashtag_hint">ハッシュタグ (#をつけない)</string>
<string name="select_list_title">リストを選択</string>
<string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string>
<string name="poll_info_time_absolute">%1$sに終了</string>

View file

@ -356,7 +356,7 @@
<string name="description_visibility_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_hint">#를 제외한 해시태그</string>
<string name="pick_hashtag_hint">#를 제외한 해시태그</string>
<string name="notifications_clear">알림 지우기</string>
<string name="notifications_apply_filter">필터</string>
<string name="filter_apply">적용</string>

View file

@ -531,7 +531,7 @@
<string name="action_post_failed">Augšupielāde neizdevās</string>
<string name="action_post_failed_show_drafts">Rādīt melnrakstus</string>
<string name="action_post_failed_do_nothing">Aizvākt</string>
<string name="edit_hashtag_hint">Tēmturis bez #</string>
<string name="pick_hashtag_hint">Tēmturis bez #</string>
<string name="status_edit_info">%1$s laboja</string>
<string name="action_browser_login">Pieslēgties ar pārlūku</string>
<string name="description_login">Strādā vairumā gadījumu. Dati netiek nopludināti uz citām lietotnēm.</string>

View file

@ -325,7 +325,7 @@
<string name="description_visibility_private">Følgere</string>
<string name="description_visibility_direct">Direkte</string>
<string name="hint_list_name">Listenavn</string>
<string name="edit_hashtag_hint">Emneord uten #</string>
<string name="pick_hashtag_hint">Emneord uten #</string>
<string name="notifications_clear">Fjern</string>
<string name="notifications_apply_filter">Filter</string>
<string name="filter_apply">Bruk</string>

View file

@ -342,7 +342,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_hint">Hashtag zonder #</string>
<string name="pick_hashtag_hint">Hashtag zonder #</string>
<string name="action_delete_and_redraft">Verwijderen en herschrijven</string>
<string name="dialog_redraft_post_warning">Dit bericht verwijderen en herschrijven\?</string>
<string name="notifications_clear">Verwijderen</string>

View file

@ -342,7 +342,7 @@
<string name="description_visibility_private">Seguidors</string>
<string name="description_visibility_direct">Dirècte</string>
<string name="hint_list_name">Nom de la lista</string>
<string name="edit_hashtag_hint">Etiquetas sens #</string>
<string name="pick_hashtag_hint">Etiquetas sens #</string>
<string name="compose_shortcut_long_label">Escriure una publicacion</string>
<string name="compose_shortcut_short_label">Redactar</string>
<string name="action_delete_and_redraft">Suprimir e reformular</string>

View file

@ -375,7 +375,7 @@
<string name="description_visibility_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_hint">Hashtag bez #</string>
<string name="pick_hashtag_hint">Hashtag bez #</string>
<string name="notifications_clear">Wyczyść</string>
<string name="notifications_apply_filter">Filtr</string>
<string name="filter_apply">Zastosuj</string>

View file

@ -351,7 +351,7 @@
<string name="description_visibility_unlisted">Não-listado</string>
<string name="description_visibility_direct">Direto</string>
<string name="hint_list_name">Nome da lista</string>
<string name="edit_hashtag_hint">Hashtag sem #</string>
<string name="pick_hashtag_hint">Hashtag sem #</string>
<string name="notifications_clear">Excluir notificações</string>
<string name="notifications_apply_filter">Filtrar notificações</string>
<string name="filter_apply">Salvar</string>

View file

@ -396,7 +396,7 @@
<string name="description_poll">Votação com as opções: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Nome da lista</string>
<string name="add_hashtag_title">Adicionar hashtag</string>
<string name="edit_hashtag_hint">Hashtag sem #</string>
<string name="pick_hashtag_hint">Hashtag sem #</string>
<string name="hashtags">Hashtags</string>
<string name="select_list_title">Selecionar lista</string>
<string name="list">Lista</string>

View file

@ -374,7 +374,7 @@
<string name="description_visibility_private">Подписчики</string>
<string name="description_visibility_direct">Личное упоминание</string>
<string name="hint_list_name">Название списка</string>
<string name="edit_hashtag_hint">Хэштег без #</string>
<string name="pick_hashtag_hint">Хэштег без #</string>
<string name="notifications_clear">Очистить</string>
<string name="notifications_apply_filter">Фильтр</string>
<string name="filter_apply">Применить</string>

View file

@ -427,7 +427,7 @@
<string name="list">सूचिः</string>
<string name="select_list_title">सूचिरवचीयताम्</string>
<string name="hashtags">निश्रेणिचिह्नशीर्षकाः</string>
<string name="edit_hashtag_hint"># चिह्नं विना प्रचलितम्</string>
<string name="pick_hashtag_hint"># चिह्नं विना प्रचलितम्</string>
<string name="add_hashtag_title">प्रचलितं युज्यताम्</string>
<string name="hint_list_name">सूचिनाम</string>
<string name="description_poll">मतदाने मतानि- %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -319,7 +319,7 @@
<string name="description_visibility_private">Sledilci</string>
<string name="description_visibility_direct">Neposredno</string>
<string name="hint_list_name">Ime seznama</string>
<string name="edit_hashtag_hint">Ključnik brez #</string>
<string name="pick_hashtag_hint">Ključnik brez #</string>
<string name="notifications_clear">Počisti</string>
<string name="notifications_apply_filter">Filter</string>
<string name="filter_apply">Uporabi</string>

View file

@ -348,7 +348,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_hint">Hashtag utan #</string>
<string name="pick_hashtag_hint">Hashtag utan #</string>
<string name="compose_shortcut_long_label">Skriv inlägg</string>
<string name="compose_shortcut_short_label">Skriv</string>
<string name="notifications_clear">Radera</string>

View file

@ -56,7 +56,7 @@
<string name="list">รายการ</string>
<string name="select_list_title">เลือกรายการ</string>
<string name="hashtags">แฮชแท็ก</string>
<string name="edit_hashtag_hint">แฮชแท็กโดยไม่มี #</string>
<string name="pick_hashtag_hint">แฮชแท็กโดยไม่มี #</string>
<string name="add_hashtag_title">เพิ่มแฮชแท็ก</string>
<string name="hint_list_name">ชื่อรายการ</string>
<string name="description_poll">โพลกับตัวเลือก: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -346,7 +346,7 @@
<string name="description_visibility_direct">Doğrudan</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_hint"># Etiket olmadan</string>
<string name="pick_hashtag_hint"># Etiket olmadan</string>
<string name="notifications_clear">Sil</string>
<string name="notifications_apply_filter">Süzgeçle</string>
<string name="filter_apply">Uygula</string>

View file

@ -483,7 +483,7 @@
<string name="notifications_clear">Видалити</string>
<string name="list">Список</string>
<string name="select_list_title">Вибрати список</string>
<string name="edit_hashtag_hint">Хештег без #</string>
<string name="pick_hashtag_hint">Хештег без #</string>
<string name="add_hashtag_title">Додати хештег</string>
<string name="hint_list_name">Назва списку</string>
<string name="description_poll">Опитування з варіантами: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -363,7 +363,7 @@
<string name="list">Danh sách</string>
<string name="select_list_title">Chọn danh sách</string>
<string name="hashtags">Hashtag</string>
<string name="edit_hashtag_hint">Không cần dấu #</string>
<string name="pick_hashtag_hint">Không cần dấu #</string>
<string name="add_hashtag_title">Thêm hashtag</string>
<string name="hint_list_name">Tên danh sách</string>
<string name="description_poll">Lượt bình chọn: %1$s, %2$s, %3$s, %4$s; %5$s</string>

View file

@ -350,7 +350,7 @@
私信
</string>
<string name="hint_list_name">列表名</string>
<string name="edit_hashtag_hint">话题名(不含前面的 # 号)</string>
<string name="pick_hashtag_hint">话题名(不含前面的 # 号)</string>
<string name="notifications_clear">删除</string>
<string name="notifications_apply_filter">筛选</string>
<string name="filter_apply">应用</string>

View file

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

View file

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

View file

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

View file

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

View file

@ -82,7 +82,7 @@
<string name="title_edits">Edits</string>
<string name="dialog_follow_hashtag_title">Follow hashtag</string>
<string name="dialog_follow_hashtag_hint">#hashtag</string>
<string name="follow_hashtag_success">Followed hashtag %1$s</string>
<string name="post_username_format">\@%1$s</string>
<string name="post_boosted_format">%1$s boosted</string>
@ -616,7 +616,7 @@
<string name="hint_list_name">List name</string>
<string name="add_hashtag_title">Add hashtag</string>
<string name="edit_hashtag_hint">Hashtag without #</string>
<string name="pick_hashtag_hint">Hashtag without #</string>
<string name="hashtags">Hashtags</string>
<string name="select_list_title">Select list</string>
<string name="select_list_manage">Manage lists</string>