3488 improve profile list (#3507)
Fixes #3488 Working with lists from a profile page and in the normal "lists view" from the drawer now use the same fragment view code. (also) RFC regarding joining different list lists 
This commit is contained in:
parent
6494247301
commit
0698333665
37 changed files with 250 additions and 380 deletions
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
/* Copyright Tusky contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
|
|
@ -23,9 +23,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
|
@ -35,14 +33,14 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
||||
import com.keylesspalace.tusky.databinding.DialogListBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemListBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
import com.keylesspalace.tusky.util.BindingHolder
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
|
|
@ -54,18 +52,12 @@ import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_OTHER
|
|||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.INITIAL
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADED
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADING
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by charlag on 1/4/18.
|
||||
*/
|
||||
// TODO use the ListSelectionFragment (and/or its adapter or binding) here; but keep the LoadingState from here (?)
|
||||
|
||||
class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||
|
||||
|
|
@ -214,9 +206,9 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
).show()
|
||||
}
|
||||
|
||||
private fun onListSelected(listId: String, listTitle: String) {
|
||||
private fun onListSelected(list: MastoList) {
|
||||
startActivityWithSlideInAnimation(
|
||||
StatusListActivity.newListIntent(this, listId, listTitle)
|
||||
StatusListActivity.newListIntent(this, list.id, list.title)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -255,42 +247,28 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
}
|
||||
|
||||
private inner class ListsAdapter :
|
||||
ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||
ListAdapter<MastoList, BindingHolder<ItemListBinding>>(ListsDiffer) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
||||
return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
|
||||
.let(this::ListViewHolder)
|
||||
.apply {
|
||||
val iconColor = MaterialColors.getColor(nameTextView, android.R.attr.textColorTertiary)
|
||||
val context = nameTextView.context
|
||||
val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor }
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): BindingHolder<ItemListBinding> {
|
||||
return BindingHolder(ItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemListBinding>, position: Int) {
|
||||
val item = getItem(position)
|
||||
holder.binding.listName.text = item.title
|
||||
|
||||
holder.binding.moreButton.apply {
|
||||
visible(true)
|
||||
setOnClickListener {
|
||||
onMore(item, holder.binding.moreButton)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
|
||||
holder.nameTextView.text = getItem(position).title
|
||||
}
|
||||
|
||||
private inner class ListViewHolder(view: View) :
|
||||
RecyclerView.ViewHolder(view),
|
||||
View.OnClickListener {
|
||||
val nameTextView: TextView = view.findViewById(R.id.list_name_textview)
|
||||
val moreButton: ImageButton = view.findViewById(R.id.editListButton)
|
||||
|
||||
init {
|
||||
view.setOnClickListener(this)
|
||||
moreButton.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (v == itemView) {
|
||||
val list = getItem(bindingAdapterPosition)
|
||||
onListSelected(list.id, list.title)
|
||||
} else {
|
||||
onMore(getItem(bindingAdapterPosition), v)
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
onListSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,18 +15,10 @@
|
|||
|
||||
package com.keylesspalace.tusky
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
|
|
@ -38,34 +30,29 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.TransitionManager
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialArcMotion
|
||||
import com.google.android.material.transition.MaterialContainerTransform
|
||||
import com.keylesspalace.tusky.adapter.ItemInteractionListener
|
||||
import com.keylesspalace.tusky.adapter.TabAdapter
|
||||
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.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.getDimension
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.unsafeLazy
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.regex.Pattern
|
||||
import javax.inject.Inject
|
||||
|
||||
class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener {
|
||||
class TabPreferenceActivity : BaseActivity(), Injectable, HasAndroidInjector, ItemInteractionListener, ListSelectionFragment.ListSelectionListener {
|
||||
|
||||
@Inject
|
||||
lateinit var mastodonApi: MastodonApi
|
||||
|
|
@ -73,6 +60,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
@Inject
|
||||
lateinit var eventHub: EventHub
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
private val binding by viewBinding(ActivityTabPreferenceBinding::inflate)
|
||||
|
||||
private lateinit var currentTabs: MutableList<TabData>
|
||||
|
|
@ -267,81 +257,24 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
editText.requestFocus()
|
||||
}
|
||||
|
||||
private var listSelectDialog: ListSelectionFragment? = null
|
||||
|
||||
private fun showSelectListDialog() {
|
||||
val adapter = object : ArrayAdapter<MastoList>(this, android.R.layout.simple_list_item_1) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
getItem(position)?.let { item -> (view as TextView).text = item.title }
|
||||
return view
|
||||
}
|
||||
}
|
||||
listSelectDialog = ListSelectionFragment.newInstance(null)
|
||||
listSelectDialog?.show(supportFragmentManager, null)
|
||||
|
||||
val statusLayout = LinearLayout(this)
|
||||
statusLayout.gravity = Gravity.CENTER
|
||||
val progress = ProgressBar(this)
|
||||
val preferredPadding = getDimension(this, androidx.appcompat.R.attr.dialogPreferredPadding)
|
||||
progress.setPadding(preferredPadding, 0, preferredPadding, 0)
|
||||
progress.visible(false)
|
||||
|
||||
val noListsText = TextView(this)
|
||||
noListsText.setPadding(preferredPadding, 0, preferredPadding, 0)
|
||||
noListsText.text = getText(R.string.select_list_empty)
|
||||
noListsText.visible(false)
|
||||
|
||||
statusLayout.addView(progress)
|
||||
statusLayout.addView(noListsText)
|
||||
|
||||
val dialogBuilder = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.select_list_title)
|
||||
.setNeutralButton(R.string.select_list_manage) { _, _ ->
|
||||
val listIntent = Intent(applicationContext, ListsActivity::class.java)
|
||||
startActivity(listIntent)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setView(statusLayout)
|
||||
.setAdapter(adapter) { _, position ->
|
||||
adapter.getItem(position)?.let { item ->
|
||||
val newTab = createTabDataFromId(LIST, listOf(item.id, item.title))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
}
|
||||
}
|
||||
|
||||
val showProgressBarJob = getProgressBarJob(progress, 500)
|
||||
showProgressBarJob.start()
|
||||
|
||||
val dialog = dialogBuilder.show()
|
||||
|
||||
lifecycleScope.launch {
|
||||
mastodonApi.getLists().fold(
|
||||
{ lists ->
|
||||
showProgressBarJob.cancel()
|
||||
adapter.addAll(lists)
|
||||
if (lists.isEmpty()) {
|
||||
noListsText.show()
|
||||
}
|
||||
},
|
||||
{ throwable ->
|
||||
dialog.hide()
|
||||
Log.e("TabPreferenceActivity", "failed to load lists", throwable)
|
||||
Snackbar.make(binding.root, R.string.error_list_load, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
private fun getProgressBarJob(progressView: View, delayMs: Long) = this.lifecycleScope.launch(
|
||||
start = CoroutineStart.LAZY
|
||||
) {
|
||||
try {
|
||||
delay(delayMs)
|
||||
progressView.show()
|
||||
awaitCancellation()
|
||||
} finally {
|
||||
progressView.hide()
|
||||
}
|
||||
override fun onListSelected(list: MastoList) {
|
||||
listSelectDialog?.dismiss()
|
||||
listSelectDialog = null
|
||||
|
||||
val newTab = createTabDataFromId(LIST, listOf(list.id, list.title))
|
||||
currentTabs.add(newTab)
|
||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||
updateAvailableTabs()
|
||||
saveTabs()
|
||||
}
|
||||
|
||||
private fun validateHashtag(input: CharSequence?): Boolean {
|
||||
|
|
@ -419,6 +352,8 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
}
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
||||
companion object {
|
||||
private const val MIN_TAB_COUNT = 2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ import com.keylesspalace.tusky.EditProfileActivity
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.StatusListActivity
|
||||
import com.keylesspalace.tusky.ViewMediaActivity
|
||||
import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment
|
||||
import com.keylesspalace.tusky.components.account.list.ListSelectionFragment
|
||||
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.components.report.ReportActivity
|
||||
|
|
@ -991,7 +991,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
|
|||
return true
|
||||
}
|
||||
R.id.action_add_or_remove_from_list -> {
|
||||
ListsForAccountFragment.newInstance(viewModel.accountId).show(supportFragmentManager, null)
|
||||
ListSelectionFragment.newInstance(viewModel.accountId).show(supportFragmentManager, null)
|
||||
return true
|
||||
}
|
||||
R.id.action_mute_domain -> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright 2022 kyori19
|
||||
/* Copyright Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
|
|
@ -16,79 +16,99 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.account.list
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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.ListsActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.databinding.FragmentListsForAccountBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemAddOrRemoveFromListBinding
|
||||
import com.keylesspalace.tusky.databinding.FragmentListsListBinding
|
||||
import com.keylesspalace.tusky.databinding.ItemListBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.MastoList
|
||||
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.CoroutineStart
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class ListsForAccountFragment : DialogFragment(), Injectable {
|
||||
class ListSelectionFragment : DialogFragment(), Injectable {
|
||||
|
||||
interface ListSelectionListener {
|
||||
fun onListSelected(list: MastoList)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private val viewModel: ListsForAccountViewModel by viewModels { viewModelFactory }
|
||||
private val binding by viewBinding(FragmentListsForAccountBinding::bind)
|
||||
|
||||
private var _binding: FragmentListsListBinding? = null
|
||||
|
||||
// This property is only valid between onCreateDialog and onDestroyView
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val adapter = Adapter()
|
||||
|
||||
private var selectListener: ListSelectionListener? = null
|
||||
private var accountId: String? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
selectListener = context as? ListSelectionListener
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NORMAL, R.style.TuskyDialogFragmentStyle)
|
||||
|
||||
viewModel.setup(requireArguments().getString(ARG_ACCOUNT_ID)!!)
|
||||
accountId = requireArguments().getString(ARG_ACCOUNT_ID)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
dialog?.apply {
|
||||
window?.setLayout(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val context = requireContext()
|
||||
|
||||
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 = FragmentListsListBinding.inflate(layoutInflater)
|
||||
binding.listsView.adapter = adapter
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
val dialogBuilder = AlertDialog.Builder(context)
|
||||
.setView(binding.root)
|
||||
.setTitle(R.string.select_list_title)
|
||||
.setNeutralButton(R.string.select_list_manage) { _, _ ->
|
||||
val listIntent = Intent(context, ListsActivity::class.java)
|
||||
startActivity(listIntent)
|
||||
}
|
||||
.setNegativeButton(if (accountId != null) R.string.button_done else android.R.string.cancel, null)
|
||||
|
||||
val dialog = dialogBuilder.create()
|
||||
|
||||
val showProgressBarJob = getProgressBarJob(binding.progressBar, 500)
|
||||
showProgressBarJob.start()
|
||||
|
||||
// TODO change this to a (single) LoadState like elsewhere?
|
||||
lifecycleScope.launch {
|
||||
viewModel.states.collectLatest { states ->
|
||||
binding.progressBar.hide()
|
||||
showProgressBarJob.cancel()
|
||||
if (states.isEmpty()) {
|
||||
binding.messageView.show()
|
||||
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.no_lists) {
|
||||
load()
|
||||
}
|
||||
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.no_lists)
|
||||
} else {
|
||||
binding.listsView.show()
|
||||
adapter.submitList(states)
|
||||
|
|
@ -96,9 +116,11 @@ class ListsForAccountFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
lifecycleScope.launch {
|
||||
viewModel.loadError.collectLatest { error ->
|
||||
Log.e(TAG, "failed to load lists", error)
|
||||
binding.progressBar.hide()
|
||||
showProgressBarJob.cancel()
|
||||
binding.listsView.hide()
|
||||
binding.messageView.apply {
|
||||
show()
|
||||
|
|
@ -107,20 +129,20 @@ class ListsForAccountFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
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)
|
||||
viewModel.addAccountToList(accountId!!, 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)
|
||||
viewModel.removeAccountFromList(accountId!!, error.listId)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
|
@ -128,18 +150,35 @@ class ListsForAccountFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
binding.doneButton.setOnClickListener {
|
||||
dismiss()
|
||||
lifecycleScope.launch {
|
||||
load()
|
||||
}
|
||||
|
||||
load()
|
||||
return dialog
|
||||
}
|
||||
|
||||
private fun getProgressBarJob(progressView: View, delayMs: Long) = this.lifecycleScope.launch(
|
||||
start = CoroutineStart.LAZY
|
||||
) {
|
||||
try {
|
||||
delay(delayMs)
|
||||
progressView.show()
|
||||
awaitCancellation()
|
||||
} finally {
|
||||
progressView.hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun load() {
|
||||
binding.progressBar.show()
|
||||
binding.listsView.hide()
|
||||
binding.messageView.hide()
|
||||
viewModel.load()
|
||||
viewModel.load(accountId)
|
||||
}
|
||||
|
||||
private object Differ : DiffUtil.ItemCallback<AccountListState>() {
|
||||
|
|
@ -159,42 +198,55 @@ class ListsForAccountFragment : DialogFragment(), Injectable {
|
|||
}
|
||||
|
||||
inner class Adapter :
|
||||
ListAdapter<AccountListState, BindingHolder<ItemAddOrRemoveFromListBinding>>(Differ) {
|
||||
ListAdapter<AccountListState, BindingHolder<ItemListBinding>>(Differ) {
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): BindingHolder<ItemAddOrRemoveFromListBinding> {
|
||||
val binding =
|
||||
ItemAddOrRemoveFromListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
): BindingHolder<ItemListBinding> {
|
||||
return BindingHolder(ItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemAddOrRemoveFromListBinding>, position: Int) {
|
||||
override fun onBindViewHolder(holder: BindingHolder<ItemListBinding>, 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.listName.text = item.list.title
|
||||
accountId?.let { accountId ->
|
||||
holder.binding.addButton.apply {
|
||||
visible(!item.includesAccount)
|
||||
setOnClickListener {
|
||||
viewModel.addAccountToList(accountId, item.list.id)
|
||||
}
|
||||
}
|
||||
holder.binding.removeButton.apply {
|
||||
visible(item.includesAccount)
|
||||
setOnClickListener {
|
||||
viewModel.removeAccountFromList(accountId, item.list.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
holder.binding.removeButton.apply {
|
||||
visible(item.includesAccount)
|
||||
setOnClickListener {
|
||||
viewModel.removeAccountFromList(item.list.id)
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
selectListener?.onListSelected(item.list)
|
||||
|
||||
accountId?.let { accountId ->
|
||||
if (item.includesAccount) {
|
||||
viewModel.removeAccountFromList(accountId, item.list.id)
|
||||
} else {
|
||||
viewModel.addAccountToList(accountId, item.list.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ListsListFragment"
|
||||
private const val ARG_ACCOUNT_ID = "accountId"
|
||||
|
||||
fun newInstance(accountId: String): ListsForAccountFragment {
|
||||
fun newInstance(accountId: String?): ListSelectionFragment {
|
||||
val args = Bundle().apply {
|
||||
putString(ARG_ACCOUNT_ID, accountId)
|
||||
}
|
||||
return ListsForAccountFragment().apply { arguments = args }
|
||||
return ListSelectionFragment().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,6 @@ 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
|
||||
|
|
@ -54,8 +52,6 @@ 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
|
||||
|
||||
|
|
@ -65,24 +61,21 @@ class ListsForAccountViewModel @Inject constructor(
|
|||
private val _actionError = MutableSharedFlow<ActionError>(1)
|
||||
val actionError: SharedFlow<ActionError> = _actionError
|
||||
|
||||
fun setup(accountId: String) {
|
||||
this.accountId = accountId
|
||||
}
|
||||
|
||||
fun load() {
|
||||
fun load(accountId: String?) {
|
||||
_loadError.resetReplayCache()
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
val (all, includes) = listOf(
|
||||
async { mastodonApi.getLists() },
|
||||
async { mastodonApi.getListsIncludesAccount(accountId) }
|
||||
).awaitAll()
|
||||
val all = mastodonApi.getLists().getOrThrow()
|
||||
var includes: List<MastoList> = emptyList()
|
||||
if (accountId != null) {
|
||||
includes = mastodonApi.getListsIncludesAccount(accountId).getOrThrow()
|
||||
}
|
||||
|
||||
_states.emit(
|
||||
all.getOrThrow().map { list ->
|
||||
all.map { listState ->
|
||||
AccountListState(
|
||||
list = list,
|
||||
includesAccount = includes.getOrThrow().any { it.id == list.id }
|
||||
list = listState,
|
||||
includesAccount = includes.any { it.id == listState.id }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -93,7 +86,9 @@ class ListsForAccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun addAccountToList(listId: String) {
|
||||
// TODO there is no "progress" visible for these
|
||||
|
||||
fun addAccountToList(accountId: String, listId: String) {
|
||||
_actionError.resetReplayCache()
|
||||
viewModelScope.launch {
|
||||
mastodonApi.addAccountToList(listId, listOf(accountId))
|
||||
|
|
@ -114,7 +109,7 @@ class ListsForAccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun removeAccountFromList(listId: String) {
|
||||
fun removeAccountFromList(accountId: String, listId: String) {
|
||||
_actionError.resetReplayCache()
|
||||
viewModelScope.launch {
|
||||
mastodonApi.deleteAccountFromList(listId, listOf(accountId))
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ abstract class ActivitiesModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract fun contributesLicenseActivity(): LicenseActivity
|
||||
|
||||
@ContributesAndroidInjector
|
||||
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
|
||||
abstract fun contributesTabPreferenceActivity(): TabPreferenceActivity
|
||||
|
||||
@ContributesAndroidInjector
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
package com.keylesspalace.tusky.di
|
||||
|
||||
import com.keylesspalace.tusky.AccountsInListFragment
|
||||
import com.keylesspalace.tusky.components.account.list.ListsForAccountFragment
|
||||
import com.keylesspalace.tusky.components.account.list.ListSelectionFragment
|
||||
import com.keylesspalace.tusky.components.account.media.AccountMediaFragment
|
||||
import com.keylesspalace.tusky.components.accountlist.AccountListFragment
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
||||
|
|
@ -97,7 +97,7 @@ abstract class FragmentBuildersModule {
|
|||
abstract fun preferencesFragment(): PreferencesFragment
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract fun listsForAccountFragment(): ListsForAccountFragment
|
||||
abstract fun listsForAccountFragment(): ListSelectionFragment
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract fun trendingTagsFragment(): TrendingTagsFragment
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue