diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt b/husky/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt index 55a856c..a09ac67 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt @@ -1,3 +1,23 @@ +/* + * 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 @@ -8,187 +28,44 @@ 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 kotlinx.android.synthetic.main.activity_filters.* -import kotlinx.android.synthetic.main.dialog_filter.* -import kotlinx.android.synthetic.main.toolbar_basic.* +import java.io.IOException +import javax.inject.Inject import okhttp3.ResponseBody import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import java.io.IOException -import javax.inject.Inject -class FiltersActivity: BaseActivity() { +class FiltersActivity : BaseActivity() { + @Inject lateinit var api: MastodonApi @Inject lateinit var eventHub: EventHub - private lateinit var context : String + private val binding by viewBinding(ActivityFiltersBinding::inflate) + + private lateinit var context: String private lateinit var filters: MutableList - private lateinit var dialog: AlertDialog companion object { const val FILTERS_CONTEXT = "filters_context" const val FILTERS_TITLE = "filters_title" } - 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() { - dialog = AlertDialog.Builder(this@FiltersActivity) - .setTitle(R.string.filter_addition_dialog_title) - .setView(R.layout.dialog_filter) - .setPositiveButton(android.R.string.ok){ _, _ -> - createFilter(dialog.phraseEditText.text.toString(), dialog.phraseWholeWord.isChecked) - } - .setNeutralButton(android.R.string.cancel, null) - .create() - dialog.show() - dialog.phraseWholeWord.isChecked = true - } - - private fun setupEditDialogForItem(itemIndex: Int) { - 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, dialog.phraseEditText.text.toString(), oldFilter.context, - oldFilter.expiresAt, oldFilter.irreversible, dialog.phraseWholeWord.isChecked) - updateFilter(newFilter, itemIndex) - } - .setNegativeButton(R.string.filter_dialog_remove_button) { _, _ -> - deleteFilter(itemIndex) - } - .setNeutralButton(android.R.string.cancel, null) - .create() - dialog.show() - - // Need to show the dialog before referencing any elements from its view - val filter = filters[itemIndex] - dialog.phraseEditText.setText(filter.phrase) - dialog.phraseWholeWord.isChecked = filter.wholeWord - } - - private fun refreshFilterDisplay() { - filtersView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, filters.map { filter -> filter.phrase }) - filtersView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ -> setupEditDialogForItem(position) } - } - - private fun loadFilters() { - - filterMessageView.hide() - filtersView.hide() - addFilterButton.hide() - 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() - - filtersView.show() - addFilterButton.show() - filterProgressBar.hide() - } else { - filterProgressBar.hide() - filterMessageView.show() - filterMessageView.setup(R.drawable.elephant_error, - R.string.error_generic) { loadFilters() } - } - } - - override fun onFailure(call: Call>, t: Throwable) { - filterProgressBar.hide() - filterMessageView.show() - if (t is IOException) { - filterMessageView.setup(R.drawable.elephant_offline, - R.string.error_network) { loadFilters() } - } else { - filterMessageView.setup(R.drawable.elephant_error, - R.string.error_generic) { loadFilters() } - } - } - }) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_filters) + setContentView(binding.root) setupToolbarBackArrow() - addFilterButton.setOnClickListener { + binding.addFilterButton.setOnClickListener { showAddFilterDialog() } @@ -197,8 +74,203 @@ class FiltersActivity: BaseActivity() { 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(toolbar) + setSupportActionBar(binding.includedToolbar.toolbar) supportActionBar?.run { // Back button setDisplayHomeAsUpEnabled(true) @@ -208,7 +280,7 @@ class FiltersActivity: BaseActivity() { // Activate back arrow in toolbar override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { + when(item.itemId) { android.R.id.home -> { onBackPressed() return true diff --git a/husky/app/src/main/res/layout/activity_filters.xml b/husky/app/src/main/res/layout/activity_filters.xml index 9d51725..c0b181e 100644 --- a/husky/app/src/main/res/layout/activity_filters.xml +++ b/husky/app/src/main/res/layout/activity_filters.xml @@ -7,7 +7,9 @@ android:layout_height="match_parent" tools:context="com.keylesspalace.tusky.FiltersActivity"> - + - \ No newline at end of file +