2024-01-03 21:17:03 +01:00
|
|
|
/* Copyright Tusky contributors
|
2019-03-16 13:36:16 +01:00
|
|
|
*
|
|
|
|
* 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>.
|
|
|
|
*/
|
|
|
|
|
2018-01-06 19:01:37 +01:00
|
|
|
package com.keylesspalace.tusky
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
import android.app.Dialog
|
2018-01-06 19:01:37 +01:00
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
2021-06-28 21:13:24 +02:00
|
|
|
import android.widget.PopupMenu
|
2021-03-07 19:05:51 +01:00
|
|
|
import androidx.activity.viewModels
|
2019-03-16 13:36:16 +01:00
|
|
|
import androidx.annotation.StringRes
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
2023-03-10 20:27:24 +01:00
|
|
|
import androidx.core.widget.doOnTextChanged
|
2022-05-18 18:45:35 +02:00
|
|
|
import androidx.lifecycle.lifecycleScope
|
2021-06-28 21:13:24 +02:00
|
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
|
|
import androidx.recyclerview.widget.DividerItemDecoration
|
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
2019-03-16 13:36:16 +01:00
|
|
|
import androidx.recyclerview.widget.ListAdapter
|
|
|
|
import com.google.android.material.snackbar.Snackbar
|
2021-03-07 19:05:51 +01:00
|
|
|
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
2023-08-06 20:29:34 +02:00
|
|
|
import com.keylesspalace.tusky.databinding.DialogListBinding
|
2024-01-03 21:17:03 +01:00
|
|
|
import com.keylesspalace.tusky.databinding.ItemListBinding
|
2018-03-27 19:47:00 +02:00
|
|
|
import com.keylesspalace.tusky.di.Injectable
|
2019-03-16 13:36:16 +01:00
|
|
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
2018-01-06 19:01:37 +01:00
|
|
|
import com.keylesspalace.tusky.entity.MastoList
|
2024-01-03 21:17:03 +01:00
|
|
|
import com.keylesspalace.tusky.util.BindingHolder
|
2021-06-28 21:13:24 +02:00
|
|
|
import com.keylesspalace.tusky.util.hide
|
|
|
|
import com.keylesspalace.tusky.util.show
|
|
|
|
import com.keylesspalace.tusky.util.viewBinding
|
|
|
|
import com.keylesspalace.tusky.util.visible
|
2019-03-16 13:36:16 +01:00
|
|
|
import com.keylesspalace.tusky.viewmodel.ListsViewModel
|
2021-06-28 21:13:24 +02:00
|
|
|
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event
|
|
|
|
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_NETWORK
|
|
|
|
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
|
2019-03-16 13:36:16 +01:00
|
|
|
import dagger.android.DispatchingAndroidInjector
|
2019-06-09 19:48:04 +02:00
|
|
|
import dagger.android.HasAndroidInjector
|
2018-03-27 19:47:00 +02:00
|
|
|
import javax.inject.Inject
|
2024-01-04 17:00:55 +01:00
|
|
|
import kotlinx.coroutines.launch
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
// TODO use the ListSelectionFragment (and/or its adapter or binding) here; but keep the LoadingState from here (?)
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2019-06-09 19:48:04 +02:00
|
|
|
class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2018-03-27 19:47:00 +02:00
|
|
|
@Inject
|
2019-03-16 13:36:16 +01:00
|
|
|
lateinit var viewModelFactory: ViewModelFactory
|
|
|
|
|
|
|
|
@Inject
|
2019-06-09 19:48:04 +02:00
|
|
|
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
|
2018-03-27 19:47:00 +02:00
|
|
|
|
2021-03-07 19:05:51 +01:00
|
|
|
private val viewModel: ListsViewModel by viewModels { viewModelFactory }
|
|
|
|
|
|
|
|
private val binding by viewBinding(ActivityListsBinding::inflate)
|
|
|
|
|
2018-01-06 19:01:37 +01:00
|
|
|
private val adapter = ListsAdapter()
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
2021-03-07 19:05:51 +01:00
|
|
|
setContentView(binding.root)
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2021-03-07 19:05:51 +01:00
|
|
|
setSupportActionBar(binding.includedToolbar.toolbar)
|
2019-03-16 13:36:16 +01:00
|
|
|
supportActionBar?.apply {
|
|
|
|
title = getString(R.string.title_lists)
|
|
|
|
setDisplayHomeAsUpEnabled(true)
|
|
|
|
setDisplayShowHomeEnabled(true)
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.listsRecycler.adapter = adapter
|
|
|
|
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
|
|
|
|
binding.listsRecycler.addItemDecoration(
|
2021-06-28 21:13:24 +02:00
|
|
|
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
|
|
|
)
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2023-03-30 19:29:42 +02:00
|
|
|
binding.swipeRefreshLayout.setOnRefreshListener { viewModel.retryLoading() }
|
|
|
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
|
|
|
|
2022-05-18 18:45:35 +02:00
|
|
|
lifecycleScope.launch {
|
|
|
|
viewModel.state.collect(this@ListsActivity::update)
|
|
|
|
}
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
viewModel.retryLoading()
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.addListButton.setOnClickListener {
|
2019-03-16 13:36:16 +01:00
|
|
|
showlistNameDialog(null)
|
|
|
|
}
|
|
|
|
|
2022-05-18 18:45:35 +02:00
|
|
|
lifecycleScope.launch {
|
|
|
|
viewModel.events.collect { event ->
|
2021-06-28 21:13:24 +02:00
|
|
|
when (event) {
|
|
|
|
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
|
2023-08-06 20:29:34 +02:00
|
|
|
Event.UPDATE_ERROR -> showMessage(R.string.error_rename_list)
|
2021-06-28 21:13:24 +02:00
|
|
|
Event.DELETE_ERROR -> showMessage(R.string.error_delete_list)
|
2019-03-16 13:36:16 +01:00
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|
2022-05-18 18:45:35 +02:00
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
private fun showlistNameDialog(list: MastoList?) {
|
2023-10-26 11:21:04 +02:00
|
|
|
val binding = DialogListBinding.inflate(layoutInflater).apply {
|
|
|
|
replyPolicySpinner.setSelection(MastoList.ReplyPolicy.from(list?.repliesPolicy).ordinal)
|
|
|
|
}
|
2019-03-16 13:36:16 +01:00
|
|
|
val dialog = AlertDialog.Builder(this)
|
2023-08-06 20:29:34 +02:00
|
|
|
.setView(binding.root)
|
2021-06-28 21:13:24 +02:00
|
|
|
.setPositiveButton(
|
2023-03-13 13:16:39 +01:00
|
|
|
if (list == null) {
|
|
|
|
R.string.action_create_list
|
|
|
|
} else {
|
|
|
|
R.string.action_rename_list
|
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
) { _, _ ->
|
2023-10-26 11:21:04 +02:00
|
|
|
onPickedDialogName(
|
|
|
|
binding.nameText.text.toString(),
|
|
|
|
list?.id,
|
|
|
|
binding.exclusiveCheckbox.isChecked,
|
|
|
|
MastoList.ReplyPolicy.entries[binding.replyPolicySpinner.selectedItemPosition].policy
|
|
|
|
)
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.show()
|
2019-03-16 13:36:16 +01:00
|
|
|
|
2023-08-06 20:29:34 +02:00
|
|
|
binding.nameText.let { editText ->
|
|
|
|
editText.doOnTextChanged { s, _, _, _ ->
|
|
|
|
dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled = s?.isNotBlank() == true
|
|
|
|
}
|
|
|
|
editText.setText(list?.title)
|
|
|
|
editText.text?.let { editText.setSelection(it.length) }
|
|
|
|
}
|
|
|
|
|
|
|
|
list?.let {
|
|
|
|
if (it.exclusive == null) {
|
|
|
|
binding.exclusiveCheckbox.visible(false)
|
|
|
|
} else {
|
|
|
|
binding.exclusiveCheckbox.isChecked = it.exclusive
|
|
|
|
}
|
2019-03-16 13:36:16 +01:00
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2021-01-18 11:40:13 +01:00
|
|
|
private fun showListDeleteDialog(list: MastoList) {
|
|
|
|
AlertDialog.Builder(this)
|
2021-06-28 21:13:24 +02:00
|
|
|
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
|
|
|
|
.setPositiveButton(R.string.action_delete) { _, _ ->
|
|
|
|
viewModel.deleteList(list.id)
|
|
|
|
}
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.show()
|
2021-01-18 11:40:13 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
private fun update(state: ListsViewModel.State) {
|
|
|
|
adapter.submitList(state.lists)
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.progressBar.visible(state.loadingState == LOADING)
|
2023-03-30 19:29:42 +02:00
|
|
|
binding.swipeRefreshLayout.isRefreshing = state.loadingState == LOADING
|
2019-01-28 19:02:31 +01:00
|
|
|
when (state.loadingState) {
|
2021-03-07 19:05:51 +01:00
|
|
|
INITIAL, LOADING -> binding.messageView.hide()
|
2019-01-28 19:02:31 +01:00
|
|
|
ERROR_NETWORK -> {
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.messageView.show()
|
2023-08-05 15:39:07 +02:00
|
|
|
binding.messageView.setup(R.drawable.errorphant_offline, R.string.error_network) {
|
2019-01-28 19:02:31 +01:00
|
|
|
viewModel.retryLoading()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ERROR_OTHER -> {
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.messageView.show()
|
2023-08-05 15:39:07 +02:00
|
|
|
binding.messageView.setup(R.drawable.errorphant_error, R.string.error_generic) {
|
2019-01-28 19:02:31 +01:00
|
|
|
viewModel.retryLoading()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LOADED ->
|
|
|
|
if (state.lists.isEmpty()) {
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.messageView.show()
|
2021-06-28 21:13:24 +02:00
|
|
|
binding.messageView.setup(
|
2023-03-13 13:16:39 +01:00
|
|
|
R.drawable.elephant_friend_empty,
|
|
|
|
R.string.message_empty,
|
2021-06-28 21:13:24 +02:00
|
|
|
null
|
|
|
|
)
|
2023-04-18 11:24:35 +02:00
|
|
|
binding.messageView.showHelp(R.string.help_empty_lists)
|
2019-01-28 19:02:31 +01:00
|
|
|
} else {
|
2021-03-07 19:05:51 +01:00
|
|
|
binding.messageView.hide()
|
2019-01-28 19:02:31 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
private fun showMessage(@StringRes messageId: Int) {
|
|
|
|
Snackbar.make(
|
2023-03-13 13:16:39 +01:00
|
|
|
binding.listsRecycler,
|
|
|
|
messageId,
|
|
|
|
Snackbar.LENGTH_SHORT
|
2019-03-16 13:36:16 +01:00
|
|
|
).show()
|
|
|
|
}
|
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
private fun onListSelected(list: MastoList) {
|
2018-07-31 21:25:25 +02:00
|
|
|
startActivityWithSlideInAnimation(
|
2024-01-03 21:17:03 +01:00
|
|
|
StatusListActivity.newListIntent(this, list.id, list.title)
|
2021-06-28 21:13:24 +02:00
|
|
|
)
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
private fun openListSettings(list: MastoList) {
|
|
|
|
AccountsInListFragment.newInstance(list.id, list.title).show(supportFragmentManager, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun renameListDialog(list: MastoList) {
|
|
|
|
showlistNameDialog(list)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun onMore(list: MastoList, view: View) {
|
|
|
|
PopupMenu(view.context, view).apply {
|
|
|
|
inflate(R.menu.list_actions)
|
|
|
|
setOnMenuItemClickListener { item ->
|
|
|
|
when (item.itemId) {
|
|
|
|
R.id.list_edit -> openListSettings(list)
|
2023-08-06 20:29:34 +02:00
|
|
|
R.id.list_update -> renameListDialog(list)
|
2021-01-18 11:40:13 +01:00
|
|
|
R.id.list_delete -> showListDeleteDialog(list)
|
2019-03-16 13:36:16 +01:00
|
|
|
else -> return@setOnMenuItemClickListener false
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private object ListsDiffer : DiffUtil.ItemCallback<MastoList>() {
|
|
|
|
override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean {
|
|
|
|
return oldItem.id == newItem.id
|
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2019-03-16 13:36:16 +01:00
|
|
|
override fun areContentsTheSame(oldItem: MastoList, newItem: MastoList): Boolean {
|
|
|
|
return oldItem == newItem
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
2019-03-16 13:36:16 +01:00
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2021-06-28 21:13:24 +02:00
|
|
|
private inner class ListsAdapter :
|
2024-01-03 21:17:03 +01:00
|
|
|
ListAdapter<MastoList, BindingHolder<ItemListBinding>>(ListsDiffer) {
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
override fun onCreateViewHolder(
|
|
|
|
parent: ViewGroup,
|
|
|
|
viewType: Int
|
|
|
|
): BindingHolder<ItemListBinding> {
|
|
|
|
return BindingHolder(ItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
override fun onBindViewHolder(holder: BindingHolder<ItemListBinding>, position: Int) {
|
|
|
|
val item = getItem(position)
|
|
|
|
holder.binding.listName.text = item.title
|
2018-01-06 19:01:37 +01:00
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
holder.binding.moreButton.apply {
|
|
|
|
visible(true)
|
|
|
|
setOnClickListener {
|
|
|
|
onMore(item, holder.binding.moreButton)
|
|
|
|
}
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
|
2024-01-03 21:17:03 +01:00
|
|
|
holder.itemView.setOnClickListener {
|
|
|
|
onListSelected(item)
|
2018-01-06 19:01:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-16 13:36:16 +01:00
|
|
|
|
2024-01-04 17:00:55 +01:00
|
|
|
private fun onPickedDialogName(
|
|
|
|
name: String,
|
|
|
|
listId: String?,
|
|
|
|
exclusive: Boolean,
|
|
|
|
replyPolicy: String
|
|
|
|
) {
|
2019-03-16 13:36:16 +01:00
|
|
|
if (listId == null) {
|
2023-10-26 11:21:04 +02:00
|
|
|
viewModel.createNewList(name, exclusive, replyPolicy)
|
2019-03-16 13:36:16 +01:00
|
|
|
} else {
|
2023-10-26 11:21:04 +02:00
|
|
|
viewModel.updateList(listId, name, exclusive, replyPolicy)
|
2019-03-16 13:36:16 +01:00
|
|
|
}
|
|
|
|
}
|
2021-03-07 19:05:51 +01:00
|
|
|
|
|
|
|
override fun androidInjector() = dispatchingAndroidInjector
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
fun newIntent(context: Context) = Intent(context, ListsActivity::class.java)
|
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|