package com.keylesspalace.tusky import android.os.Bundle import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.databinding.ActivityFiltersBinding import com.keylesspalace.tusky.databinding.DialogFilterBinding import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding import kotlinx.coroutines.launch import kotlinx.coroutines.rx3.await import okhttp3.ResponseBody import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.io.IOException import javax.inject.Inject class FiltersActivity : BaseActivity() { @Inject lateinit var api: MastodonApi @Inject lateinit var eventHub: EventHub private val binding by viewBinding(ActivityFiltersBinding::inflate) private lateinit var context: String private lateinit var filters: MutableList override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) setSupportActionBar(binding.includedToolbar.toolbar) supportActionBar?.run { // Back button setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) } binding.addFilterButton.setOnClickListener { showAddFilterDialog() } title = intent?.getStringExtra(FILTERS_TITLE) context = intent?.getStringExtra(FILTERS_CONTEXT)!! loadFilters() } private fun updateFilter(filter: Filter, itemIndex: Int) { api.updateFilter(filter.id, filter.phrase, filter.context, filter.irreversible, filter.wholeWord, filter.expiresAt) .enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Toast.makeText(this@FiltersActivity, "Error updating filter '${filter.phrase}'", Toast.LENGTH_SHORT).show() } override fun onResponse(call: Call, response: Response) { val updatedFilter = response.body()!! if (updatedFilter.context.contains(context)) { filters[itemIndex] = updatedFilter } else { filters.removeAt(itemIndex) } refreshFilterDisplay() eventHub.dispatch(PreferenceChangedEvent(context)) } }) } private fun deleteFilter(itemIndex: Int) { val filter = filters[itemIndex] if (filter.context.size == 1) { // This is the only context for this filter; delete it api.deleteFilter(filters[itemIndex].id).enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Toast.makeText(this@FiltersActivity, "Error updating filter '${filters[itemIndex].phrase}'", Toast.LENGTH_SHORT).show() } override fun onResponse(call: Call, response: Response) { filters.removeAt(itemIndex) refreshFilterDisplay() eventHub.dispatch(PreferenceChangedEvent(context)) } }) } else { // Keep the filter, but remove it from this context val oldFilter = filters[itemIndex] val newFilter = Filter( oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context }, oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord ) updateFilter(newFilter, itemIndex) } } private fun createFilter(phrase: String, wholeWord: Boolean) { api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val filterResponse = response.body() if (response.isSuccessful && filterResponse != null) { filters.add(filterResponse) refreshFilterDisplay() eventHub.dispatch(PreferenceChangedEvent(context)) } else { Toast.makeText(this@FiltersActivity, "Error creating filter '$phrase'", Toast.LENGTH_SHORT).show() } } override fun onFailure(call: Call, t: Throwable) { Toast.makeText(this@FiltersActivity, "Error creating filter '$phrase'", Toast.LENGTH_SHORT).show() } }) } private fun showAddFilterDialog() { val binding = DialogFilterBinding.inflate(layoutInflater) binding.phraseWholeWord.isChecked = true AlertDialog.Builder(this@FiltersActivity) .setTitle(R.string.filter_addition_dialog_title) .setView(binding.root) .setPositiveButton(android.R.string.ok) { _, _ -> createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked) } .setNeutralButton(android.R.string.cancel, null) .show() } private fun setupEditDialogForItem(itemIndex: Int) { val binding = DialogFilterBinding.inflate(layoutInflater) val filter = filters[itemIndex] binding.phraseEditText.setText(filter.phrase) binding.phraseWholeWord.isChecked = filter.wholeWord AlertDialog.Builder(this@FiltersActivity) .setTitle(R.string.filter_edit_dialog_title) .setView(binding.root) .setPositiveButton(R.string.filter_dialog_update_button) { _, _ -> val oldFilter = filters[itemIndex] val newFilter = Filter( oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context, oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked ) updateFilter(newFilter, itemIndex) } .setNegativeButton(R.string.filter_dialog_remove_button) { _, _ -> deleteFilter(itemIndex) } .setNeutralButton(android.R.string.cancel, null) .show() } private fun refreshFilterDisplay() { binding.filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase }) binding.filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) } } private fun loadFilters() { binding.filterMessageView.hide() binding.filtersView.hide() binding.addFilterButton.hide() binding.filterProgressBar.show() lifecycleScope.launch { val newFilters = try { api.getFilters().await() } catch (t: Exception) { binding.filterProgressBar.hide() binding.filterMessageView.show() if (t is IOException) { binding.filterMessageView.setup( R.drawable.elephant_offline, R.string.error_network ) { loadFilters() } } else { binding.filterMessageView.setup( R.drawable.elephant_error, R.string.error_generic ) { loadFilters() } } return@launch } filters = newFilters.filter { it.context.contains(context) }.toMutableList() refreshFilterDisplay() binding.filtersView.show() binding.addFilterButton.show() binding.filterProgressBar.hide() } } companion object { const val FILTERS_CONTEXT = "filters_context" const val FILTERS_TITLE = "filters_title" } }