/* * Husky -- A Pleroma client for Android * * Copyright (C) 2022 The Husky Developers * Copyright (C) 2019 Tusky Contributors * * 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. * * This program 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 this program. If not, see . */ package com.keylesspalace.tusky import android.os.Bundle import android.view.MenuItem import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.core.extensions.viewBinding 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 java.io.IOException import javax.inject.Inject import okhttp3.ResponseBody import retrofit2.Call import retrofit2.Callback import retrofit2.Response 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 companion object { const val FILTERS_CONTEXT = "filters_context" const val FILTERS_TITLE = "filters_title" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) setupToolbarBackArrow() 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, MastodonApi.PostFilter( 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(MastodonApi.PostFilter(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 dialogBind = DialogFilterBinding.inflate(layoutInflater) dialogBind.phraseWholeWord.isChecked = true val dialog = AlertDialog.Builder(this@FiltersActivity) .setTitle(R.string.filter_addition_dialog_title) .setView(dialogBind.root) .setPositiveButton(android.R.string.ok) { _, _ -> createFilter( dialogBind.phraseEditText.text.toString(), dialogBind.phraseWholeWord.isChecked ) } .setNeutralButton(android.R.string.cancel, null) .create() dialog.show() } private fun setupEditDialogForItem(itemIndex: Int) { // Need to show the dialog before referencing any elements from its view val filter = filters[itemIndex] val dialogBind = DialogFilterBinding.inflate(layoutInflater) dialogBind.phraseEditText.setText(filter.phrase) dialogBind.phraseWholeWord.isChecked = filter.wholeWord val dialog = AlertDialog.Builder(this@FiltersActivity) .setTitle(R.string.filter_edit_dialog_title) .setView(R.layout.dialog_filter) .setPositiveButton(R.string.filter_dialog_update_button) { _, _ -> val oldFilter = filters[itemIndex] val newFilter = Filter( oldFilter.id, dialogBind.phraseEditText.text.toString(), oldFilter.context, oldFilter.expiresAt, oldFilter.irreversible, dialogBind.phraseWholeWord.isChecked ) updateFilter(newFilter, itemIndex) } .setNegativeButton(R.string.filter_dialog_remove_button) { _, _ -> deleteFilter(itemIndex) } .setNeutralButton(android.R.string.cancel, null) .create() dialog.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() api.getFilters().enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { val filterResponse = response.body() if(response.isSuccessful && filterResponse != null) { filters = filterResponse.filter { filter -> filter.context.contains(context) } .toMutableList() refreshFilterDisplay() binding.filtersView.show() binding.addFilterButton.show() binding.filterProgressBar.hide() } else { binding.filterProgressBar.hide() binding.filterMessageView.show() binding.filterMessageView.setup( R.drawable.elephant_error, R.string.error_generic ) { loadFilters() } } } override fun onFailure(call: Call>, t: Throwable) { 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() } } } }) } private fun setupToolbarBackArrow() { setSupportActionBar(binding.includedToolbar.toolbar) supportActionBar?.run { // Back button setDisplayHomeAsUpEnabled(true) setDisplayShowHomeEnabled(true) } } // Activate back arrow in toolbar override fun onOptionsItemSelected(item: MenuItem): Boolean { when(item.itemId) { android.R.id.home -> { onBackPressed() return true } } return super.onOptionsItemSelected(item) } }