Yuito-app-android/app/src/main/java/com/keylesspalace/tusky/components/filters/EditFilterActivity.kt

275 lines
10 KiB
Kotlin

package com.keylesspalace.tusky.components.filters
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.view.size
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.switchmaterial.SwitchMaterial
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.databinding.ActivityEditFilterBinding
import com.keylesspalace.tusky.databinding.DialogFilterBinding
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.FilterKeyword
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.viewBinding
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
class EditFilterActivity : BaseActivity() {
@Inject
lateinit var api: MastodonApi
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var viewModelFactory: ViewModelFactory
private val binding by viewBinding(ActivityEditFilterBinding::inflate)
private val viewModel: EditFilterViewModel by viewModels { viewModelFactory }
private lateinit var filter: Filter
private var originalFilter: Filter? = null
private lateinit var contextSwitches: Map<SwitchMaterial, Filter.Kind>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
originalFilter = intent?.getParcelableExtra(FILTER_TO_EDIT)
filter = originalFilter ?: Filter("", "", listOf(), null, Filter.Action.WARN.action, listOf())
binding.apply {
contextSwitches = mapOf(
filterContextHome to Filter.Kind.HOME,
filterContextNotifications to Filter.Kind.NOTIFICATIONS,
filterContextPublic to Filter.Kind.PUBLIC,
filterContextThread to Filter.Kind.THREAD,
filterContextAccount to Filter.Kind.ACCOUNT
)
}
setContentView(binding.root)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.run {
// Back button
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
setTitle(
if (originalFilter == null) {
R.string.filter_addition_title
} else {
R.string.filter_edit_title
}
)
binding.actionChip.setOnClickListener { showAddKeywordDialog() }
binding.filterSaveButton.setOnClickListener { saveChanges() }
for (switch in contextSwitches.keys) {
switch.setOnCheckedChangeListener { _, isChecked ->
val context = contextSwitches[switch]!!
if (isChecked) {
viewModel.addContext(context)
} else {
viewModel.removeContext(context)
}
validateSaveButton()
}
}
binding.filterTitle.doAfterTextChanged { editable ->
viewModel.setTitle(editable.toString())
validateSaveButton()
}
binding.filterActionWarn.setOnCheckedChangeListener { _, checked ->
viewModel.setAction(
if (checked) {
Filter.Action.WARN
} else {
Filter.Action.HIDE
}
)
}
binding.filterDurationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
viewModel.setDuration(
if (originalFilter?.expiresAt == null) {
position
} else {
position - 1
}
)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
viewModel.setDuration(0)
}
}
validateSaveButton()
if (originalFilter == null) {
binding.filterActionWarn.isChecked = true
} else {
loadFilter()
}
observeModel()
}
private fun observeModel() {
lifecycleScope.launch {
viewModel.title.collect { title ->
if (title != binding.filterTitle.text.toString()) {
// We also get this callback when typing in the field,
// which messes with the cursor focus
binding.filterTitle.setText(title)
}
}
}
lifecycleScope.launch {
viewModel.keywords.collect { keywords ->
updateKeywords(keywords)
}
}
lifecycleScope.launch {
viewModel.contexts.collect { contexts ->
for (entry in contextSwitches) {
entry.key.isChecked = contexts.contains(entry.value)
}
}
}
lifecycleScope.launch {
viewModel.action.collect { action ->
when (action) {
Filter.Action.HIDE -> binding.filterActionHide.isChecked = true
else -> binding.filterActionWarn.isChecked = true
}
}
}
}
// Populate the UI from the filter's members
private fun loadFilter() {
viewModel.load(filter)
if (filter.expiresAt != null) {
val durationNames = listOf(getString(R.string.duration_no_change)) + resources.getStringArray(R.array.filter_duration_names)
binding.filterDurationSpinner.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, durationNames)
}
}
private fun updateKeywords(newKeywords: List<FilterKeyword>) {
newKeywords.forEachIndexed { index, filterKeyword ->
val chip = binding.keywordChips.getChildAt(index).takeUnless {
it.id == R.id.actionChip
} as Chip? ?: Chip(this).apply {
setCloseIconResource(R.drawable.ic_cancel_24dp)
isCheckable = false
binding.keywordChips.addView(this, binding.keywordChips.size - 1)
}
chip.text = if (filterKeyword.wholeWord) {
binding.root.context.getString(
R.string.filter_keyword_display_format,
filterKeyword.keyword
)
} else {
filterKeyword.keyword
}
chip.isCloseIconVisible = true
chip.setOnClickListener {
showEditKeywordDialog(newKeywords[index])
}
chip.setOnCloseIconClickListener {
viewModel.deleteKeyword(newKeywords[index])
}
}
while (binding.keywordChips.size - 1 > newKeywords.size) {
binding.keywordChips.removeViewAt(newKeywords.size)
}
filter = filter.copy(keywords = newKeywords)
validateSaveButton()
}
private fun showAddKeywordDialog() {
val binding = DialogFilterBinding.inflate(layoutInflater)
binding.phraseWholeWord.isChecked = true
AlertDialog.Builder(this)
.setTitle(R.string.filter_keyword_addition_title)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
viewModel.addKeyword(
FilterKeyword(
"",
binding.phraseEditText.text.toString(),
binding.phraseWholeWord.isChecked
)
)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun showEditKeywordDialog(keyword: FilterKeyword) {
val binding = DialogFilterBinding.inflate(layoutInflater)
binding.phraseEditText.setText(keyword.keyword)
binding.phraseWholeWord.isChecked = keyword.wholeWord
AlertDialog.Builder(this)
.setTitle(R.string.filter_edit_keyword_title)
.setView(binding.root)
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
viewModel.modifyKeyword(
keyword,
keyword.copy(
keyword = binding.phraseEditText.text.toString(),
wholeWord = binding.phraseWholeWord.isChecked
)
)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun validateSaveButton() {
binding.filterSaveButton.isEnabled = viewModel.validate()
}
private fun saveChanges() {
// TODO use a progress bar here (see EditProfileActivity/activity_edit_profile.xml for example)?
lifecycleScope.launch {
if (viewModel.saveChanges(this@EditFilterActivity)) {
finish()
} else {
Snackbar.make(binding.root, "Error saving filter '${viewModel.title.value}'", Snackbar.LENGTH_SHORT).show()
}
}
}
companion object {
const val FILTER_TO_EDIT = "FilterToEdit"
// Mastodon *stores* the absolute date in the filter,
// but create/edit take a number of seconds (relative to the time the operation is posted)
fun getSecondsForDurationIndex(index: Int, context: Context?, default: Date? = null): Int? {
return when (index) {
-1 -> if (default == null) { default } else { ((default.time - System.currentTimeMillis()) / 1000).toInt() }
0 -> null
else -> context?.resources?.getIntArray(R.array.filter_duration_values)?.get(index)
}
}
}
}