Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/fragment/filter/AddEditItemFragment.kt

267 lines
11 KiB
Kotlin

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment.filter
import android.accounts.AccountManager
import android.app.Dialog
import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.CheckBox
import android.widget.Toast
import kotlinx.android.synthetic.main.dialog_filter_rule_editor.*
import org.mariotaku.ktextension.ContentValues
import org.mariotaku.ktextension.set
import org.mariotaku.ktextension.string
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.ComposeAutoCompleteAdapter
import org.mariotaku.twidere.adapter.SourceAutoCompleteAdapter
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.util.isAdvancedFiltersEnabled
import org.mariotaku.twidere.fragment.BaseDialogFragment
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
import org.mariotaku.twidere.model.filter.FilterScopesHolder
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
class AddEditItemFragment : BaseDialogFragment() {
private val contentUri: Uri
get() = arguments?.getParcelable(EXTRA_URI)!!
private val rowId: Long
get() = arguments?.getLong(EXTRA_ID, -1) ?: -1
private val defaultValue: String?
get() = arguments?.getString(EXTRA_VALUE)
private val defaultScopes: FilterScopesHolder
get() = FilterScopesHolder(filterMasks, arguments?.getInt(EXTRA_SCOPE, FilterScope.DEFAULT) ?: FilterScope.DEFAULT)
private val filterMasks: Int
get() = when (contentUri) {
Filters.Users.CONTENT_URI -> FilterScope.VALID_MASKS_USERS
Filters.Keywords.CONTENT_URI -> FilterScope.VALID_MASKS_KEYWORDS
Filters.Sources.CONTENT_URI -> FilterScope.VALID_MASKS_SOURCES
Filters.Links.CONTENT_URI -> FilterScope.VALID_MASKS_LINKS
else -> 0
}
private val canEditValue: Boolean
get() = contentUri != Filters.Users.CONTENT_URI
private var Dialog.value: String?
get() = editText.string?.takeIf(String::isNotEmpty)
set(value) {
editText.string = value
}
private var Dialog.scopes: FilterScopesHolder?
get() = defaultScopes.also {
if (extraFeaturesService.isAdvancedFiltersEnabled) {
saveScopes(it)
}
}
set(value) {
loadScopes(value ?: defaultScopes)
}
private var Dialog.advancedExpanded: Boolean
get() = advancedContainer.visibility == View.VISIBLE
set(value) {
advancedContainer.setVisible(value)
advancedCollapseIndicator.rotation = if (value) 90f else 0f
}
private fun handlePositiveClick(button: View) {
val scope = dialog?.scopes ?: return
if (!canEditValue) {
saveScopeOnly(scope)
} else {
val value = dialog!!.value?.takeIf(String::isNotEmpty)
if (value == null) {
dialog!!.editText.error = getString(R.string.hint_error_field_required)
return
}
saveItem(value, scope)
}
dismiss()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
builder.setView(R.layout.dialog_filter_rule_editor)
if (arguments?.getLong(EXTRA_ID, -1) ?: -1 >= 0) {
builder.setTitle(R.string.action_edit_filter_rule)
} else {
builder.setTitle(R.string.action_add_filter_rule)
}
builder.setPositiveButton(android.R.string.ok, null)
builder.setNegativeButton(android.R.string.cancel, null)
val dialog = builder.create()
dialog.applyOnShow {
applyTheme()
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
editText.setAdapter(when (contentUri) {
Filters.Sources.CONTENT_URI -> SourceAutoCompleteAdapter(requireActivity())
Filters.Users.CONTENT_URI -> ComposeAutoCompleteAdapter(requireActivity(), requestManager).apply {
val am = AccountManager.get(activity)
account = AccountUtils.getDefaultAccountDetails(requireActivity(), am, false)
}
else -> null
})
editText.threshold = 1
editText.isEnabled = canEditValue
advancedToggle.setOnClickListener {
advancedExpanded = !advancedExpanded
}
positiveButton.setOnClickListener(this@AddEditItemFragment::handlePositiveClick)
advancedContainer.children.filterIsInstance<CheckBox>().forEach {
val checkBox = it as CheckBox
checkBox.setOnClickListener onClick@ {
if (extraFeaturesService.isAdvancedFiltersEnabled) return@onClick
// Revert check state
checkBox.isChecked = !checkBox.isChecked
val df = ExtraFeaturesIntroductionDialogFragment.create(
ExtraFeaturesService.FEATURE_ADVANCED_FILTERS)
df.setTargetFragment(this@AddEditItemFragment, REQUEST_CHANGE_SCOPE_PURCHASE)
df.show(parentFragmentManager, ExtraFeaturesIntroductionDialogFragment.FRAGMENT_TAG)
}
}
if (savedInstanceState == null) {
value = defaultValue
scopes = defaultScopes
advancedExpanded = false
editText.setSelection(editText.length().coerceAtLeast(0))
if (editText.isEnabled) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE)
as InputMethodManager
imm.showSoftInput(editText, 0)
}
} else {
value = savedInstanceState.getString(EXTRA_VALUE)
scopes = savedInstanceState.getParcelable(EXTRA_SCOPE)
}
}
return dialog
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(EXTRA_VALUE, dialog?.value)
outState.putParcelable(EXTRA_SCOPE, dialog?.scopes)
}
private fun Dialog.saveScopes(scopes: FilterScopesHolder) {
targetText.saveScope(scopes, FilterScope.TARGET_TEXT)
targetName.saveScope(scopes, FilterScope.TARGET_NAME)
targetDescription.saveScope(scopes, FilterScope.TARGET_DESCRIPTION)
scopeHome.saveScope(scopes, FilterScope.HOME)
scopeInteractions.saveScope(scopes, FilterScope.INTERACTIONS)
scopeMessages.saveScope(scopes, FilterScope.MESSAGES)
scopeSearchResults.saveScope(scopes, FilterScope.SEARCH_RESULTS)
scopeOther.saveScope(scopes, FilterScope.UGC_TIMELINE)
}
private fun Dialog.loadScopes(scopes: FilterScopesHolder) {
labelTarget.setVisible(scopes.hasMask(FilterScope.MASK_TARGET))
targetText.loadScope(scopes, FilterScope.TARGET_TEXT)
targetName.loadScope(scopes, FilterScope.TARGET_NAME)
targetDescription.loadScope(scopes, FilterScope.TARGET_DESCRIPTION)
labelScope.setVisible(scopes.hasMask(FilterScope.MASK_SCOPE))
scopeHome.loadScope(scopes, FilterScope.HOME)
scopeInteractions.loadScope(scopes, FilterScope.INTERACTIONS)
scopeMessages.loadScope(scopes, FilterScope.MESSAGES)
scopeSearchResults.loadScope(scopes, FilterScope.SEARCH_RESULTS)
scopeOther.loadScope(scopes, FilterScope.UGC_TIMELINE)
}
private fun CheckBox.saveScope(scopes: FilterScopesHolder, scope: Int) {
if (!isEnabled || visibility != View.VISIBLE) return
scopes[scope] = isChecked
}
private fun CheckBox.loadScope(scopes: FilterScopesHolder, scope: Int) {
if (scope in scopes) {
isEnabled = true
visibility = View.VISIBLE
} else {
isEnabled = false
visibility = View.GONE
return
}
isChecked = scopes[scope]
}
private fun saveScopeOnly(scopes: FilterScopesHolder) {
val resolver = context?.contentResolver
val contentUri = contentUri
val rowId = rowId
if (rowId < 0) return
val values = ContentValues {
this[Filters.SCOPE] = scopes.value
}
val idWhere = Expression.equals(Filters._ID, rowId).sql
resolver?.update(contentUri, values, idWhere, null)
}
private fun saveItem(value: String, scopes: FilterScopesHolder) {
val resolver = context?.contentResolver
val uri = contentUri
val rowId = rowId
val values = ContentValues {
this[Filters.VALUE] = value
this[Filters.SCOPE] = scopes.value
}
if (rowId >= 0) {
val valueWhere = Expression.equalsArgs(Filters.VALUE).sql
val valueWhereArgs = arrayOf(value)
val matchedId = resolver?.queryLong(uri, Filters._ID, valueWhere, valueWhereArgs,
-1)
if (matchedId != -1L && matchedId != rowId) {
Toast.makeText(context, R.string.message_toast_duplicate_filter_rule,
Toast.LENGTH_SHORT).show()
} else {
val idWhere = Expression.equals(Filters._ID, rowId).sql
resolver.update(uri, values, idWhere, null)
}
} else {
resolver?.insert(uri, values)
}
}
companion object {
private const val REQUEST_CHANGE_SCOPE_PURCHASE = 101
}
}