Add or remove from lists in AccountActivity
This commit is contained in:
parent
a9c6f69561
commit
45cc000d07
|
@ -53,6 +53,7 @@ import com.keylesspalace.tusky.EditProfileActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.StatusListActivity
|
import com.keylesspalace.tusky.StatusListActivity
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||||
import com.keylesspalace.tusky.components.report.ReportActivity
|
import com.keylesspalace.tusky.components.report.ReportActivity
|
||||||
import com.keylesspalace.tusky.databinding.ActivityAccountBinding
|
import com.keylesspalace.tusky.databinding.ActivityAccountBinding
|
||||||
|
@ -740,6 +741,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
menu.removeItem(R.id.action_report)
|
menu.removeItem(R.id.action_report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!viewModel.isSelf && followState != FollowState.FOLLOWING) {
|
||||||
|
menu.removeItem(R.id.action_add_or_remove_from_list)
|
||||||
|
}
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,6 +857,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
toggleMute()
|
toggleMute()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.action_add_or_remove_from_list -> {
|
||||||
|
ListsForAccountFragment.newInstance(viewModel.accountId).show(supportFragmentManager, null)
|
||||||
|
return true
|
||||||
|
}
|
||||||
R.id.action_mute_domain -> {
|
R.id.action_mute_domain -> {
|
||||||
toggleBlockDomain(domain)
|
toggleBlockDomain(domain)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
/* Copyright 2022 kyori19
|
||||||
|
*
|
||||||
|
* 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.components.account.list
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentListsForAccountBinding
|
||||||
|
import com.keylesspalace.tusky.databinding.ItemAddOrRemoveFromListBinding
|
||||||
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
import com.keylesspalace.tusky.util.BindingHolder
|
||||||
|
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 kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ListsForAccountFragment : DialogFragment(), Injectable {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
|
private val viewModel: ListsForAccountViewModel by viewModels { viewModelFactory }
|
||||||
|
private val binding by viewBinding(FragmentListsForAccountBinding::bind)
|
||||||
|
|
||||||
|
private val adapter = Adapter()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setStyle(STYLE_NORMAL, R.style.TuskyDialogFragmentStyle)
|
||||||
|
|
||||||
|
viewModel.setup(requireArguments().getString(ARG_ACCOUNT_ID)!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
dialog?.apply {
|
||||||
|
window?.setLayout(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_lists_for_account, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
binding.listsView.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
binding.listsView.adapter = adapter
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.states.collectLatest { states ->
|
||||||
|
binding.progressBar.hide()
|
||||||
|
if (states.isEmpty()) {
|
||||||
|
binding.messageView.show()
|
||||||
|
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.no_lists) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.listsView.show()
|
||||||
|
adapter.submitList(states)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.loadError.collectLatest { error ->
|
||||||
|
binding.progressBar.hide()
|
||||||
|
binding.listsView.hide()
|
||||||
|
binding.messageView.apply {
|
||||||
|
show()
|
||||||
|
|
||||||
|
if (error is IOException) {
|
||||||
|
setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.actionError.collectLatest { error ->
|
||||||
|
when (error.type) {
|
||||||
|
ActionError.Type.ADD -> {
|
||||||
|
Snackbar.make(binding.root, R.string.failed_to_add_to_list, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_retry) {
|
||||||
|
viewModel.addAccountToList(error.listId)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
ActionError.Type.REMOVE -> {
|
||||||
|
Snackbar.make(binding.root, R.string.failed_to_remove_from_list, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_retry) {
|
||||||
|
viewModel.removeAccountFromList(error.listId)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun load() {
|
||||||
|
binding.progressBar.show()
|
||||||
|
binding.listsView.hide()
|
||||||
|
binding.messageView.hide()
|
||||||
|
viewModel.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Differ : DiffUtil.ItemCallback<AccountListState>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: AccountListState,
|
||||||
|
newItem: AccountListState
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.list.id == newItem.list.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: AccountListState,
|
||||||
|
newItem: AccountListState
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Adapter :
|
||||||
|
ListAdapter<AccountListState, BindingHolder<ItemAddOrRemoveFromListBinding>>(Differ) {
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int,
|
||||||
|
): BindingHolder<ItemAddOrRemoveFromListBinding> {
|
||||||
|
val binding =
|
||||||
|
ItemAddOrRemoveFromListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return BindingHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: BindingHolder<ItemAddOrRemoveFromListBinding>, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
holder.binding.listNameView.text = item.list.title
|
||||||
|
holder.binding.addButton.apply {
|
||||||
|
visible(!item.includesAccount)
|
||||||
|
setOnClickListener {
|
||||||
|
viewModel.addAccountToList(item.list.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holder.binding.removeButton.apply {
|
||||||
|
visible(item.includesAccount)
|
||||||
|
setOnClickListener {
|
||||||
|
viewModel.removeAccountFromList(item.list.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_ACCOUNT_ID = "accountId"
|
||||||
|
|
||||||
|
fun newInstance(accountId: String): ListsForAccountFragment {
|
||||||
|
val args = Bundle().apply {
|
||||||
|
putString(ARG_ACCOUNT_ID, accountId)
|
||||||
|
}
|
||||||
|
return ListsForAccountFragment().apply { arguments = args }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/* Copyright 2022 kyori19
|
||||||
|
*
|
||||||
|
* 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.components.account.list
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import at.connyduck.calladapter.networkresult.getOrThrow
|
||||||
|
import at.connyduck.calladapter.networkresult.onFailure
|
||||||
|
import at.connyduck.calladapter.networkresult.onSuccess
|
||||||
|
import at.connyduck.calladapter.networkresult.runCatching
|
||||||
|
import com.keylesspalace.tusky.entity.MastoList
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class AccountListState(
|
||||||
|
val list: MastoList,
|
||||||
|
val includesAccount: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ActionError(
|
||||||
|
val error: Throwable,
|
||||||
|
val type: Type,
|
||||||
|
val listId: String,
|
||||||
|
) : Throwable(error) {
|
||||||
|
enum class Type {
|
||||||
|
ADD,
|
||||||
|
REMOVE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class ListsForAccountViewModel @Inject constructor(
|
||||||
|
private val mastodonApi: MastodonApi,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private lateinit var accountId: String
|
||||||
|
|
||||||
|
private val _states = MutableSharedFlow<List<AccountListState>>(1)
|
||||||
|
val states: SharedFlow<List<AccountListState>> = _states
|
||||||
|
|
||||||
|
private val _loadError = MutableSharedFlow<Throwable>(1)
|
||||||
|
val loadError: SharedFlow<Throwable> = _loadError
|
||||||
|
|
||||||
|
private val _actionError = MutableSharedFlow<ActionError>(1)
|
||||||
|
val actionError: SharedFlow<ActionError> = _actionError
|
||||||
|
|
||||||
|
fun setup(accountId: String) {
|
||||||
|
this.accountId = accountId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load() {
|
||||||
|
_loadError.resetReplayCache()
|
||||||
|
viewModelScope.launch {
|
||||||
|
runCatching {
|
||||||
|
val (all, includes) = listOf(
|
||||||
|
async { mastodonApi.getLists() },
|
||||||
|
async { mastodonApi.getListsIncludesAccount(accountId) },
|
||||||
|
).awaitAll()
|
||||||
|
|
||||||
|
_states.emit(
|
||||||
|
all.getOrThrow().map { list ->
|
||||||
|
AccountListState(
|
||||||
|
list = list,
|
||||||
|
includesAccount = includes.getOrThrow().any { it.id == list.id },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
_loadError.emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAccountToList(listId: String) {
|
||||||
|
_actionError.resetReplayCache()
|
||||||
|
viewModelScope.launch {
|
||||||
|
mastodonApi.addAccountToList(listId, listOf(accountId))
|
||||||
|
.onSuccess {
|
||||||
|
_states.emit(
|
||||||
|
_states.first().map { state ->
|
||||||
|
if (state.list.id == listId) {
|
||||||
|
state.copy(includesAccount = true)
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
_actionError.emit(ActionError(it, ActionError.Type.ADD, listId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAccountFromList(listId: String) {
|
||||||
|
_actionError.resetReplayCache()
|
||||||
|
viewModelScope.launch {
|
||||||
|
mastodonApi.deleteAccountFromList(listId, listOf(accountId))
|
||||||
|
.onSuccess {
|
||||||
|
_states.emit(
|
||||||
|
_states.first().map { state ->
|
||||||
|
if (state.list.id == listId) {
|
||||||
|
state.copy(includesAccount = false)
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
_actionError.emit(ActionError(it, ActionError.Type.REMOVE, listId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import com.keylesspalace.tusky.AccountsInListFragment
|
import com.keylesspalace.tusky.AccountsInListFragment
|
||||||
|
import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment
|
||||||
import com.keylesspalace.tusky.components.account.media.AccountMediaFragment
|
import com.keylesspalace.tusky.components.account.media.AccountMediaFragment
|
||||||
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
||||||
import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment
|
import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment
|
||||||
|
@ -91,4 +92,7 @@ abstract class FragmentBuildersModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun preferencesFragment(): PreferencesFragment
|
abstract fun preferencesFragment(): PreferencesFragment
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun listsForAccountFragment(): ListsForAccountFragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package com.keylesspalace.tusky.di
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.keylesspalace.tusky.components.account.AccountViewModel
|
import com.keylesspalace.tusky.components.account.AccountViewModel
|
||||||
|
import com.keylesspalace.tusky.components.account.list.ListsForAccountViewModel
|
||||||
import com.keylesspalace.tusky.components.account.media.AccountMediaViewModel
|
import com.keylesspalace.tusky.components.account.media.AccountMediaViewModel
|
||||||
import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel
|
import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeViewModel
|
import com.keylesspalace.tusky.components.compose.ComposeViewModel
|
||||||
|
@ -126,5 +127,10 @@ abstract class ViewModelModule {
|
||||||
@ViewModelKey(LoginWebViewViewModel::class)
|
@ViewModelKey(LoginWebViewViewModel::class)
|
||||||
internal abstract fun loginWebViewViewModel(viewModel: LoginWebViewViewModel): ViewModel
|
internal abstract fun loginWebViewViewModel(viewModel: LoginWebViewViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(ListsForAccountViewModel::class)
|
||||||
|
internal abstract fun listsForAccountViewModel(viewModel: ListsForAccountViewModel): ViewModel
|
||||||
|
|
||||||
// Add more ViewModels here
|
// Add more ViewModels here
|
||||||
}
|
}
|
||||||
|
|
|
@ -481,6 +481,11 @@ interface MastodonApi {
|
||||||
@GET("/api/v1/lists")
|
@GET("/api/v1/lists")
|
||||||
suspend fun getLists(): NetworkResult<List<MastoList>>
|
suspend fun getLists(): NetworkResult<List<MastoList>>
|
||||||
|
|
||||||
|
@GET("/api/v1/accounts/{id}/lists")
|
||||||
|
suspend fun getListsIncludesAccount(
|
||||||
|
@Path("id") accountId: String
|
||||||
|
): NetworkResult<List<MastoList>>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("api/v1/lists")
|
@POST("api/v1/lists")
|
||||||
suspend fun createList(
|
suspend fun createList(
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/listsView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.keylesspalace.tusky.view.BackgroundMessageView
|
||||||
|
android:id="@+id/messageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@android:color/transparent"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/elephant_error"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/listNameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
app:drawableTint="?android:attr/textColorSecondary"
|
||||||
|
app:drawableStartCompat="@drawable/ic_list"
|
||||||
|
tools:text="Example list" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/addButton"
|
||||||
|
style="@style/TuskyImageButton"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/action_add_to_list"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:src="@drawable/ic_plus_24dp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/removeButton"
|
||||||
|
style="@style/TuskyImageButton"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/action_remove_from_list"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:src="@drawable/ic_clear_24dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -18,6 +18,10 @@
|
||||||
android:title="@string/action_block"
|
android:title="@string/action_block"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_add_or_remove_from_list"
|
||||||
|
android:title="@string/action_add_or_remove_from_list"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item android:id="@+id/action_mute_domain"
|
<item android:id="@+id/action_mute_domain"
|
||||||
android:title="@string/action_mute_domain"
|
android:title="@string/action_mute_domain"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
|
@ -404,6 +404,9 @@
|
||||||
<string name="hint_search_people_list">Search for people you follow</string>
|
<string name="hint_search_people_list">Search for people you follow</string>
|
||||||
<string name="action_add_to_list">Add account to the list</string>
|
<string name="action_add_to_list">Add account to the list</string>
|
||||||
<string name="action_remove_from_list">Remove account from the list</string>
|
<string name="action_remove_from_list">Remove account from the list</string>
|
||||||
|
<string name="action_add_or_remove_from_list">Add or remove from list</string>
|
||||||
|
<string name="failed_to_add_to_list">Failed to add the account to the list</string>
|
||||||
|
<string name="failed_to_remove_from_list">Failed to remove the account from the list</string>
|
||||||
|
|
||||||
<string name="compose_active_account_description">Posting as %1$s</string>
|
<string name="compose_active_account_description">Posting as %1$s</string>
|
||||||
|
|
||||||
|
@ -624,6 +627,7 @@
|
||||||
<string name="no_drafts">You don\'t have any drafts.</string>
|
<string name="no_drafts">You don\'t have any drafts.</string>
|
||||||
<string name="no_scheduled_posts">You don\'t have any scheduled posts.</string>
|
<string name="no_scheduled_posts">You don\'t have any scheduled posts.</string>
|
||||||
<string name="no_announcements">There are no announcements.</string>
|
<string name="no_announcements">There are no announcements.</string>
|
||||||
|
<string name="no_lists">You don\'t have any lists.</string>
|
||||||
<string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string>
|
<string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string>
|
||||||
<string name="pref_title_show_self_username">Show username in toolbars</string>
|
<string name="pref_title_show_self_username">Show username in toolbars</string>
|
||||||
<string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string>
|
<string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string>
|
||||||
|
|
Loading…
Reference in New Issue