add hashtag tabs (#1145)

* add hashtag tabs

* address review feedback
main
Konrad Pozniak 5 years ago committed by GitHub
parent f63e1d3a04
commit d1e6b3b7ff
  1. 18
      app/src/main/java/com/keylesspalace/tusky/TabData.kt
  2. 85
      app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt
  3. 39
      app/src/main/java/com/keylesspalace/tusky/adapter/TabAdapter.kt
  4. 7
      app/src/main/java/com/keylesspalace/tusky/db/Converters.kt
  5. 5
      app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt
  6. 10
      app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt
  7. 11
      app/src/main/res/drawable-v26/ic_edit_chip.xml
  8. 9
      app/src/main/res/drawable/ic_edit_chip.xml
  9. 7
      app/src/main/res/drawable/ic_hashtag.xml
  10. 69
      app/src/main/res/layout/item_tab_preference.xml
  11. 5
      app/src/main/res/values/strings.xml

@ -29,20 +29,22 @@ const val NOTIFICATIONS = "Notifications"
const val LOCAL = "Local" const val LOCAL = "Local"
const val FEDERATED = "Federated" const val FEDERATED = "Federated"
const val DIRECT = "Direct" const val DIRECT = "Direct"
const val HASHTAG = "Hashtag"
data class TabData(val id: String, data class TabData(val id: String,
@StringRes val text: Int, @StringRes val text: Int,
@DrawableRes val icon: Int, @DrawableRes val icon: Int,
val fragment: () -> Fragment) val fragment: (List<String>) -> Fragment,
val arguments: List<String> = emptyList())
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
fun createTabDataFromId(id: String): TabData {
return when (id) { return when (id) {
HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp) { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) } HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) })
NOTIFICATIONS -> TabData(NOTIFICATIONS, R.string.title_notifications, R.drawable.ic_notifications_24dp) { NotificationsFragment.newInstance() } NOTIFICATIONS -> TabData(NOTIFICATIONS, R.string.title_notifications, R.drawable.ic_notifications_24dp, { NotificationsFragment.newInstance() })
LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp) { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) } 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) } 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.reblog_direct_dark) { ConversationsFragment.newInstance() } DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.reblog_direct_dark, { ConversationsFragment.newInstance() })
HASHTAG -> TabData(HASHTAG, R.string.hashtag, R.drawable.ic_hashtag, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.TAG, args.getOrNull(0).orEmpty()) }, arguments)
else -> throw IllegalArgumentException("unknown tab type") else -> throw IllegalArgumentException("unknown tab type")
} }
} }

@ -17,6 +17,8 @@ package com.keylesspalace.tusky
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatEditText
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
@ -27,16 +29,17 @@ import com.keylesspalace.tusky.adapter.TabAdapter
import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.onTextChanged
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDisposable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_tab_preference.* import kotlinx.android.synthetic.main.activity_tab_preference.*
import kotlinx.android.synthetic.main.toolbar_basic.* import kotlinx.android.synthetic.main.toolbar_basic.*
import java.util.regex.Pattern
import javax.inject.Inject import javax.inject.Inject
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDisposable
class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener { class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener {
@Inject @Inject
@ -51,6 +54,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
private val selectedItemElevation by lazy { resources.getDimension(R.dimen.selected_drag_item_elevation) } private val selectedItemElevation by lazy { resources.getDimension(R.dimen.selected_drag_item_elevation) }
private val hashtagRegex by lazy { Pattern.compile("([\\w_]*[\\p{Alpha}_][\\w_]*)", Pattern.CASE_INSENSITIVE) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -74,7 +79,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
addTabRecyclerView.adapter = addTabAdapter addTabRecyclerView.adapter = addTabAdapter
addTabRecyclerView.layoutManager = LinearLayoutManager(this) addTabRecyclerView.layoutManager = LinearLayoutManager(this)
touchHelper = ItemTouchHelper(object: ItemTouchHelper.Callback(){ touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.END) return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.END)
} }
@ -105,7 +110,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
} }
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if(actionState == ItemTouchHelper.ACTION_STATE_DRAG) { if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
viewHolder?.itemView?.elevation = selectedItemElevation viewHolder?.itemView?.elevation = selectedItemElevation
} }
} }
@ -134,37 +139,93 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
} }
override fun onTabAdded(tab: TabData) { override fun onTabAdded(tab: TabData) {
if (currentTabs.size >= MAX_TAB_COUNT) {
return
}
actionButton.isExpanded = false
if (tab.id == HASHTAG) {
showEditHashtagDialog()
return
}
currentTabs.add(tab) currentTabs.add(tab)
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1) currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
actionButton.isExpanded = false
updateAvailableTabs() updateAvailableTabs()
saveTabs() saveTabs()
} }
override fun onActionChipClicked(tab: TabData) {
showEditHashtagDialog(tab)
}
private fun showEditHashtagDialog(tab: TabData? = null) {
val editText = AppCompatEditText(this)
editText.setHint(R.string.edit_hashtag_hint)
editText.setText("")
editText.append(tab?.arguments?.first().orEmpty())
val dialog = AlertDialog.Builder(this)
.setTitle(R.string.edit_hashtag_title)
.setView(editText)
.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 = listOf(input))
val position = currentTabs.indexOf(tab)
currentTabs[position] = newTab
currentTabsAdapter.notifyItemChanged(position)
}
updateAvailableTabs()
saveTabs()
}
.create()
editText.onTextChanged { s, _, _, _ ->
val input = s.trim()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = input.isNotEmpty() && hashtagRegex.matcher(input).matches()
}
dialog.show()
editText.requestFocus()
}
private fun updateAvailableTabs() { private fun updateAvailableTabs() {
val addableTabs: MutableList<TabData> = mutableListOf() val addableTabs: MutableList<TabData> = mutableListOf()
val homeTab = createTabDataFromId(HOME) val homeTab = createTabDataFromId(HOME)
if(!currentTabs.contains(homeTab)) { if (!currentTabs.contains(homeTab)) {
addableTabs.add(homeTab) addableTabs.add(homeTab)
} }
val notificationTab = createTabDataFromId(NOTIFICATIONS) val notificationTab = createTabDataFromId(NOTIFICATIONS)
if(!currentTabs.contains(notificationTab)) { if (!currentTabs.contains(notificationTab)) {
addableTabs.add(notificationTab) addableTabs.add(notificationTab)
} }
val localTab = createTabDataFromId(LOCAL) val localTab = createTabDataFromId(LOCAL)
if(!currentTabs.contains(localTab)) { if (!currentTabs.contains(localTab)) {
addableTabs.add(localTab) addableTabs.add(localTab)
} }
val federatedTab = createTabDataFromId(FEDERATED) val federatedTab = createTabDataFromId(FEDERATED)
if(!currentTabs.contains(federatedTab)) { if (!currentTabs.contains(federatedTab)) {
addableTabs.add(federatedTab) addableTabs.add(federatedTab)
} }
val directMessagesTab = createTabDataFromId(DIRECT) val directMessagesTab = createTabDataFromId(DIRECT)
if(!currentTabs.contains(directMessagesTab)) { if (!currentTabs.contains(directMessagesTab)) {
addableTabs.add(directMessagesTab) addableTabs.add(directMessagesTab)
} }
addableTabs.add(createTabDataFromId(HASHTAG))
addTabAdapter.updateData(addableTabs) addTabAdapter.updateData(addableTabs)
maxTabsInfo.visible(addableTabs.size == 0 || currentTabs.size >= MAX_TAB_COUNT) maxTabsInfo.visible(addableTabs.size == 0 || currentTabs.size >= MAX_TAB_COUNT)
@ -211,7 +272,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if(tabsChanged) { if (tabsChanged) {
eventHub.dispatch(MainTabsChangedEvent(currentTabs)) eventHub.dispatch(MainTabsChangedEvent(currentTabs))
} }
} }

@ -19,22 +19,26 @@ import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.HASHTAG
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.TabData
import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import kotlinx.android.synthetic.main.item_tab_preference.view.* import kotlinx.android.synthetic.main.item_tab_preference.view.*
interface ItemInteractionListener { interface ItemInteractionListener {
fun onTabAdded(tab: TabData) fun onTabAdded(tab: TabData)
fun onStartDelete(viewHolder: RecyclerView.ViewHolder) fun onStartDelete(viewHolder: RecyclerView.ViewHolder)
fun onStartDrag(viewHolder: RecyclerView.ViewHolder) fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
fun onActionChipClicked(tab: TabData)
} }
class TabAdapter(var data: List<TabData>, class TabAdapter(private var data: List<TabData>,
val small: Boolean = false, private val small: Boolean = false,
val listener: ItemInteractionListener? = null) : RecyclerView.Adapter<TabAdapter.ViewHolder>() { private val listener: ItemInteractionListener? = null) : RecyclerView.Adapter<TabAdapter.ViewHolder>() {
fun updateData(newData: List<TabData>) { fun updateData(newData: List<TabData>) {
this.data = newData this.data = newData
@ -42,7 +46,7 @@ class TabAdapter(var data: List<TabData>,
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutId = if(small) { val layoutId = if (small) {
R.layout.item_tab_preference_small R.layout.item_tab_preference_small
} else { } else {
R.layout.item_tab_preference R.layout.item_tab_preference
@ -52,25 +56,42 @@ class TabAdapter(var data: List<TabData>,
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val context = holder.itemView.context
holder.itemView.textView.setText(data[position].text) holder.itemView.textView.setText(data[position].text)
val iconDrawable = ThemeUtils.getTintedDrawable(holder.itemView.context, data[position].icon, android.R.attr.textColorSecondary) val iconDrawable = ThemeUtils.getTintedDrawable(context, data[position].icon, android.R.attr.textColorSecondary)
holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconDrawable, null, null, null) holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconDrawable, null, null, null)
if(small) { if (small) {
holder.itemView.textView.setOnClickListener { holder.itemView.textView.setOnClickListener {
listener?.onTabAdded(data[position]) listener?.onTabAdded(data[position])
} }
} }
holder.itemView.imageView?.setOnTouchListener { _, event -> holder.itemView.imageView?.setOnTouchListener { _, event ->
if(event.action == MotionEvent.ACTION_DOWN) { if (event.action == MotionEvent.ACTION_DOWN) {
listener?.onStartDrag(holder) listener?.onStartDrag(holder)
true true
} else { } else {
false false
} }
} }
}
if (!small) {
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)
holder.itemView.actionChip.chipIcon = context.getDrawable(R.drawable.ic_edit_chip)
holder.itemView.actionChip.setOnClickListener {
listener?.onActionChipClicked(data[position])
}
} else {
holder.itemView.chipGroup.hide()
}
}
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return data.size return data.size

@ -58,12 +58,15 @@ class Converters {
@TypeConverter @TypeConverter
fun stringToTabData(str: String?): List<TabData>? { fun stringToTabData(str: String?): List<TabData>? {
return str?.split(";") return str?.split(";")
?.map { createTabDataFromId(it) } ?.map {
val data = it.split(":")
createTabDataFromId(data[0], data.drop(1))
}
} }
@TypeConverter @TypeConverter
fun tabDataToString(tabData: List<TabData>?): String? { fun tabDataToString(tabData: List<TabData>?): String? {
return tabData?.joinToString(";") { it.id } return tabData?.joinToString(";") { it.id + ":" + it.arguments.joinToString(":") }
} }
@TypeConverter @TypeConverter

@ -24,7 +24,8 @@ import com.keylesspalace.tusky.TabData
class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : FragmentPagerAdapter(manager) { class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : FragmentPagerAdapter(manager) {
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
return tabs[position].fragment() val tab = tabs[position]
return tab.fragment(tab.arguments)
} }
override fun getCount(): Int { override fun getCount(): Int {
@ -36,7 +37,7 @@ class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : Frag
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return tabs[position].id.hashCode().toLong() return tabs[position].hashCode() + position.toLong()
} }
override fun getItemPosition(item: Any): Int { override fun getItemPosition(item: Any): Int {

@ -34,20 +34,20 @@ fun View.visible(visible: Boolean, or: Int = View.GONE) {
} }
open class DefaultTextWatcher : TextWatcher { open class DefaultTextWatcher : TextWatcher {
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable) {
} }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
} }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
} }
} }
inline fun EditText.onTextChanged( inline fun EditText.onTextChanged(
crossinline callback: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit) { crossinline callback: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit) {
addTextChangedListener(object : DefaultTextWatcher() { addTextChangedListener(object : DefaultTextWatcher() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
callback(s, start, before, count) callback(s, start, before, count)
} }
}) })

@ -0,0 +1,11 @@
<?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>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?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>

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
</vector>

@ -1,28 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools"
android:background="?android:colorBackground" android:layout_width="match_parent"
android:orientation="horizontal" android:layout_height="wrap_content"
android:padding="16dp"> android:background="?android:colorBackground"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp">
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:src="@drawable/ic_drag_indicator_24dp" android:src="@drawable/ic_drag_indicator_24dp"
android:layout_width="wrap_content" app:layout_constraintStart_toStartOf="parent"
android:layout_height="wrap_content" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:drawablePadding="12dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="?attr/status_text_large"
app:layout_constraintBottom_toTopOf="@id/chipGroup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginBottom="16dp"
tools:drawableStart="@drawable/ic_home_24dp"
tools:text="Home" />
<com.google.android.material.chip.ChipGroup
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">
<TextView <com.google.android.material.chip.Chip
android:id="@+id/textView" android:id="@+id/actionChip"
android:textSize="?attr/status_text_large" style="@style/Widget.MaterialComponents.Chip.Action"
android:textColor="?android:attr/textColorSecondary" android:layout_width="wrap_content"
android:drawableStart="@drawable/ic_home_24dp" android:layout_height="wrap_content"
android:layout_width="0dp" android:checkable="false"
android:layout_marginStart="8dp" tools:text="add hashtag" />
android:drawablePadding="12dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</com.google.android.material.chip.ChipGroup>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -457,4 +457,9 @@
<string name="hint_list_name">List name</string> <string name="hint_list_name">List name</string>
<string name="edit_hashtag_title">Edit hashtag</string>
<string name="edit_hashtag_hint">Hashtag without #</string>
<string name="hashtag">Hashtag</string>
</resources> </resources>

Loading…
Cancel
Save