mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 19:36:08 +03:00
Show error dialog rather than toast. Add validation to keyword field for slashes and dots.
This commit is contained in:
parent
9d663413a9
commit
68eaefbc9c
5 changed files with 78 additions and 34 deletions
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.core.preference
|
package im.vector.app.core.preference
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.Editable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
@ -25,7 +26,10 @@ import androidx.core.view.children
|
||||||
import androidx.preference.PreferenceViewHolder
|
import androidx.preference.PreferenceViewHolder
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.addTextChangedListenerOnce
|
||||||
|
import im.vector.app.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
class KeywordPreference : VectorPreference {
|
class KeywordPreference : VectorPreference {
|
||||||
|
|
||||||
|
@ -36,6 +40,7 @@ class KeywordPreference : VectorPreference {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var keywordsEnabled = true
|
private var keywordsEnabled = true
|
||||||
|
private var isCurrentKeywordValid = true
|
||||||
|
|
||||||
private var _keywords: LinkedHashSet<String> = linkedSetOf()
|
private var _keywords: LinkedHashSet<String> = linkedSetOf()
|
||||||
|
|
||||||
|
@ -78,6 +83,7 @@ class KeywordPreference : VectorPreference {
|
||||||
val chipEditText = holder.findViewById(R.id.chipEditText) as? EditText ?: return
|
val chipEditText = holder.findViewById(R.id.chipEditText) as? EditText ?: return
|
||||||
val chipGroup = holder.findViewById(R.id.chipGroup) as? ChipGroup ?: return
|
val chipGroup = holder.findViewById(R.id.chipGroup) as? ChipGroup ?: return
|
||||||
val addKeywordButton = holder.findViewById(R.id.addKeywordButton) as? Button ?: return
|
val addKeywordButton = holder.findViewById(R.id.addKeywordButton) as? Button ?: return
|
||||||
|
val chipTextInputLayout = holder.findViewById(R.id.chipTextInputLayout) as? TextInputLayout ?: return
|
||||||
|
|
||||||
chipEditText.text = null
|
chipEditText.text = null
|
||||||
chipGroup.removeAllViews()
|
chipGroup.removeAllViews()
|
||||||
|
@ -90,11 +96,29 @@ class KeywordPreference : VectorPreference {
|
||||||
chipGroup.isEnabled = keywordsEnabled
|
chipGroup.isEnabled = keywordsEnabled
|
||||||
chipGroup.children.forEach { it.isEnabled = keywordsEnabled }
|
chipGroup.children.forEach { it.isEnabled = keywordsEnabled }
|
||||||
|
|
||||||
fun addKeyword(): Boolean {
|
chipEditText.addTextChangedListenerOnce(onTextChangeListener(chipTextInputLayout, addKeywordButton))
|
||||||
|
chipEditText.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId != EditorInfo.IME_ACTION_DONE) {
|
||||||
|
return@setOnEditorActionListener false
|
||||||
|
}
|
||||||
|
return@setOnEditorActionListener addKeyword(chipEditText)
|
||||||
|
}
|
||||||
|
chipEditText.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
listener?.onFocusDidChange(hasFocus)
|
||||||
|
}
|
||||||
|
|
||||||
|
addKeywordButton.setOnClickListener {
|
||||||
|
addKeyword(chipEditText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addKeyword(chipEditText: EditText): Boolean {
|
||||||
val keyword = chipEditText.text.toString().trim()
|
val keyword = chipEditText.text.toString().trim()
|
||||||
if (keyword.isEmpty()) {
|
|
||||||
|
if (!isCurrentKeywordValid || keyword.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
listener?.didAddKeyword(keyword)
|
listener?.didAddKeyword(keyword)
|
||||||
onPreferenceChangeListener?.onPreferenceChange(this, _keywords)
|
onPreferenceChangeListener?.onPreferenceChange(this, _keywords)
|
||||||
notifyChanged()
|
notifyChanged()
|
||||||
|
@ -102,18 +126,27 @@ class KeywordPreference : VectorPreference {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
chipEditText.setOnEditorActionListener { _, actionId, _ ->
|
private fun onTextChangeListener(chipTextInputLayout: TextInputLayout, addKeywordButton: Button) = object : SimpleTextWatcher() {
|
||||||
if (actionId != EditorInfo.IME_ACTION_DONE) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
return@setOnEditorActionListener false
|
val keyword = s.toString().trim()
|
||||||
|
val errorMessage = when {
|
||||||
|
keyword.startsWith(".") -> {
|
||||||
|
context.getString(R.string.settings_notification_keyword_contains_dot)
|
||||||
}
|
}
|
||||||
return@setOnEditorActionListener addKeyword()
|
keyword.contains("\\") -> {
|
||||||
|
context.getString(R.string.settings_notification_keyword_contains_invalid_character, "\\")
|
||||||
}
|
}
|
||||||
chipEditText.setOnFocusChangeListener { _, hasFocus ->
|
keyword.contains("/") -> {
|
||||||
listener?.onFocusDidChange(hasFocus)
|
context.getString(R.string.settings_notification_keyword_contains_invalid_character, "/")
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
addKeywordButton.setOnClickListener {
|
chipTextInputLayout.isErrorEnabled = errorMessage != null
|
||||||
addKeyword()
|
chipTextInputLayout.error = errorMessage
|
||||||
|
val keywordValid = errorMessage == null
|
||||||
|
addKeywordButton.isEnabled = keywordsEnabled && keywordValid
|
||||||
|
this@KeywordPreference.isCurrentKeywordValid = keywordValid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.DaggerScreenComponent
|
import im.vector.app.core.di.DaggerScreenComponent
|
||||||
import im.vector.app.core.di.HasScreenInjector
|
import im.vector.app.core.di.HasScreenInjector
|
||||||
|
@ -160,9 +161,22 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScree
|
||||||
}
|
}
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
if (errorMessage != null && errorMessage.isNotBlank()) {
|
if (errorMessage != null && errorMessage.isNotBlank()) {
|
||||||
activity?.toast(errorMessage)
|
displayErrorDialog(errorMessage)
|
||||||
}
|
}
|
||||||
hideLoadingView()
|
hideLoadingView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun displayErrorDialog(throwable: Throwable) {
|
||||||
|
displayErrorDialog(errorFormatter.toHumanReadable(throwable))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun displayErrorDialog(errorMessage: String) {
|
||||||
|
MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
.setTitle(R.string.dialog_title_error)
|
||||||
|
.setMessage(errorMessage)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import im.vector.app.core.preference.KeywordPreference
|
||||||
import im.vector.app.core.preference.VectorCheckboxPreference
|
import im.vector.app.core.preference.VectorCheckboxPreference
|
||||||
import im.vector.app.core.preference.VectorPreference
|
import im.vector.app.core.preference.VectorPreference
|
||||||
import im.vector.app.core.preference.VectorPreferenceCategory
|
import im.vector.app.core.preference.VectorPreferenceCategory
|
||||||
import im.vector.app.core.utils.toast
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -68,7 +67,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment
|
||||||
|
|
||||||
val footerPreference = findPreference<VectorPreference>("SETTINGS_KEYWORDS_FOOTER")!!
|
val footerPreference = findPreference<VectorPreference>("SETTINGS_KEYWORDS_FOOTER")!!
|
||||||
footerPreference.isIconSpaceReserved = false
|
footerPreference.isIconSpaceReserved = false
|
||||||
|
val host = this
|
||||||
keywordPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
keywordPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
val keywords = editKeywordPreference.keywords
|
val keywords = editKeywordPreference.keywords
|
||||||
val newChecked = newValue as Boolean
|
val newChecked = newValue as Boolean
|
||||||
|
@ -82,17 +81,15 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment
|
||||||
keywordPreference.isChecked = newChecked
|
keywordPreference.isChecked = newChecked
|
||||||
editKeywordPreference.isEnabled = newChecked
|
editKeywordPreference.isEnabled = newChecked
|
||||||
}
|
}
|
||||||
result.onFailure {
|
result.onFailure { failure ->
|
||||||
refreshDisplay()
|
refreshDisplay()
|
||||||
activity?.toast(errorFormatter.toHumanReadable(it))
|
host.displayErrorDialog(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val host = this
|
|
||||||
editKeywordPreference.listener = object: KeywordPreference.Listener {
|
editKeywordPreference.listener = object: KeywordPreference.Listener {
|
||||||
|
|
||||||
override fun onFocusDidChange(hasFocus: Boolean) {
|
override fun onFocusDidChange(hasFocus: Boolean) {
|
||||||
host.keywordsHasFocus = true
|
host.keywordsHasFocus = true
|
||||||
}
|
}
|
||||||
|
@ -147,37 +144,35 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment
|
||||||
val newActions = standardAction.actions ?: return
|
val newActions = standardAction.actions ?: return
|
||||||
val newRule = PushRule(actions = newActions.toJson(), pattern = keyword, enabled = enabled, ruleId = keyword)
|
val newRule = PushRule(actions = newActions.toJson(), pattern = keyword, enabled = enabled, ruleId = keyword)
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
val host = this
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
session.addPushRule(RuleKind.CONTENT, newRule)
|
session.addPushRule(RuleKind.CONTENT, newRule)
|
||||||
}
|
}
|
||||||
|
hideLoadingView()
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
hideLoadingView()
|
|
||||||
// Already added to UI, no-op on success
|
// Already added to UI, no-op on success
|
||||||
result.onFailure {
|
|
||||||
// Just display an error on failure, keywords have not been added to the UI
|
result.onFailure(host::displayErrorDialog)
|
||||||
activity?.toast(errorFormatter.toHumanReadable(it))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeKeyword(keyword: String) {
|
fun removeKeyword(keyword: String) {
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
val host = this
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
session.removePushRule(RuleKind.CONTENT, keyword)
|
session.removePushRule(RuleKind.CONTENT, keyword)
|
||||||
}
|
}
|
||||||
|
hideLoadingView()
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
hideLoadingView()
|
|
||||||
// Already added to UI, no-op on success
|
// Already added to UI, no-op on success
|
||||||
result.onFailure {
|
|
||||||
// Just display an error on failure, keywords have not been added to the UI
|
result.onFailure(host::displayErrorDialog)
|
||||||
activity?.toast(errorFormatter.toHumanReadable(it))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.app.features.settings.notifications
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import im.vector.app.core.preference.VectorCheckboxPreference
|
import im.vector.app.core.preference.VectorCheckboxPreference
|
||||||
import im.vector.app.core.utils.toast
|
|
||||||
import im.vector.app.features.settings.VectorSettingsBaseFragment
|
import im.vector.app.features.settings.VectorSettingsBaseFragment
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||||
|
@ -57,6 +56,7 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment
|
||||||
val newActions = standardAction.actions
|
val newActions = standardAction.actions
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
||||||
|
val host = this
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
session.updatePushRuleActions(kind,
|
session.updatePushRuleActions(kind,
|
||||||
|
@ -64,17 +64,17 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment
|
||||||
enabled,
|
enabled,
|
||||||
newActions)
|
newActions)
|
||||||
}
|
}
|
||||||
|
hideLoadingView()
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
hideLoadingView()
|
|
||||||
result.onSuccess {
|
result.onSuccess {
|
||||||
preference.isChecked = checked
|
preference.isChecked = checked
|
||||||
}
|
}
|
||||||
result.onFailure { failure ->
|
result.onFailure { failure ->
|
||||||
// Restore the previous value
|
// Restore the previous value
|
||||||
refreshDisplay()
|
refreshDisplay()
|
||||||
activity?.toast(errorFormatter.toHumanReadable(failure))
|
host.displayErrorDialog(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1097,6 +1097,8 @@
|
||||||
<string name="settings_notification_notify_me_for">Notify me for</string>
|
<string name="settings_notification_notify_me_for">Notify me for</string>
|
||||||
<string name="settings_notification_your_keywords">Your keywords</string>
|
<string name="settings_notification_your_keywords">Your keywords</string>
|
||||||
<string name="settings_notification_new_keyword">Add new keyword</string>
|
<string name="settings_notification_new_keyword">Add new keyword</string>
|
||||||
|
<string name="settings_notification_keyword_contains_dot">Keywords cannot start with \'.\'</string>
|
||||||
|
<string name="settings_notification_keyword_contains_invalid_character">Keywords cannot contain \'%s\'</string>
|
||||||
|
|
||||||
<string name="settings_notification_privacy">Notification privacy</string>
|
<string name="settings_notification_privacy">Notification privacy</string>
|
||||||
<string name="settings_notification_troubleshoot">Troubleshoot Notifications</string>
|
<string name="settings_notification_troubleshoot">Troubleshoot Notifications</string>
|
||||||
|
|
Loading…
Reference in a new issue