Commit ebcf5143 authored by Mygod's avatar Mygod

Handle errors in acl dialog more gracefully

parent 3975f3f4
......@@ -20,13 +20,13 @@
package com.github.shadowsocks.acl
import android.annotation.TargetApi
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.widget.DefaultItemAnimator
......@@ -34,11 +34,10 @@ import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.support.v7.widget.helper.ItemTouchHelper
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import android.widget.*
import com.futuremind.recyclerviewfastscroll.FastScroller
import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
import com.github.shadowsocks.App.Companion.app
......@@ -51,6 +50,7 @@ import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.widget.UndoSnackbarManager
import java.net.IDN
import java.net.MalformedURLException
import java.net.URL
import java.util.*
......@@ -71,6 +71,92 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
Domain,
Url;
}
private inner class AclRuleDialog(item: Any = "") : TextWatcher, AdapterView.OnItemSelectedListener {
val builder: AlertDialog.Builder
val templateSelector: Spinner
val editText: EditText
private val inputLayout: TextInputLayout
private lateinit var dialog: AlertDialog
private lateinit var positive: Button
init {
val activity = activity!!
val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
templateSelector = view.findViewById(R.id.template_selector)
editText = view.findViewById(R.id.content)
inputLayout = view.findViewById(R.id.content_layout)
when (item) {
is String -> {
val match = PATTERN_DOMAIN.find(item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
}
}
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
}
templateSelector.onItemSelectedListener = this
editText.addTextChangedListener(this)
builder = AlertDialog.Builder(activity)
.setTitle(R.string.edit_rule)
.setNegativeButton(android.R.string.cancel, null)
.setView(view)
}
fun show() {
dialog = builder.create()
dialog.show()
positive = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
validate()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable) = validate(value = s)
override fun onNothingSelected(parent: AdapterView<*>?) = throw IllegalStateException()
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate(position)
private fun validate(template: Int = templateSelector.selectedItemPosition, value: Editable = editText.text) {
val error = when (Template.values()[template]) {
Template.Generic -> if (value.isEmpty()) "" else null
Template.Domain -> try {
IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES)
null
} catch (e: IllegalArgumentException) {
e.message
}
Template.Url -> try {
URL(value.toString())
null
} catch (e: MalformedURLException) {
e.message
}
}
inputLayout.error = error
positive.isEnabled = error == null
}
fun add(): Int? {
val text = editText.text.toString()
return when (Template.values()[templateSelector.selectedItemPosition]) {
Template.Generic -> adapter.addToProxy(text)
Template.Domain -> adapter.addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
Template.Url -> adapter.addURL(URL(text))
}
}
}
private inner class AclRuleViewHolder(view: View) : RecyclerView.ViewHolder(view),
View.OnClickListener, View.OnLongClickListener {
......@@ -104,19 +190,18 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
override fun onClick(v: View?) {
if (selectedItems.isNotEmpty()) onLongClick(v) else {
val (templateSelector, editText, dialog) = createAclRuleDialog(item)
dialog
val dialog = AclRuleDialog(item)
dialog.builder
.setNeutralButton(R.string.delete, { _, _ ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
})
.setPositiveButton(android.R.string.ok, { _, _ ->
adapter.remove(item)
val index = adapter.addFromTemplate(templateSelector.selectedItemPosition,
editText.text.toString()) ?: adapter.add(item)
val index = dialog.add() ?: adapter.add(item)
if (index != null) list.post { list.scrollToPosition(index) }
})
.create().show()
dialog.show()
}
}
override fun onLongClick(p0: View?): Boolean {
......@@ -208,20 +293,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
.forEach { if (result == null) result = it }
return result
}
fun addFromTemplate(templateValue: Int, text: String): Int? {
val template = Template.values()[templateValue]
return when (template) {
Template.Generic -> addToProxy(text)
Template.Domain -> try {
addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
} catch (exc: IllegalArgumentException) {
Toast.makeText(activity, exc.message, Toast.LENGTH_SHORT).show()
null
}
Template.Url -> addURL(URL(text))
}
}
fun remove(i: Int) {
val j = i - acl.subnets.size()
......@@ -297,37 +368,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
if (selectedItems.isEmpty()) mode?.finish() else if (mode == null) mode = toolbar.startActionMode(this)
}
private fun createAclRuleDialog(item: Any = ""): Triple<Spinner, EditText, AlertDialog.Builder> {
val view = activity!!.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
val templateSelector = view.findViewById<Spinner>(R.id.template_selector)
val editText = view.findViewById<EditText>(R.id.content)
when (item) {
is String -> {
val match = PATTERN_DOMAIN.find(item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
}
}
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
}
return Triple(templateSelector, editText, AlertDialog.Builder(activity!!)
.setTitle(R.string.edit_rule)
.setNegativeButton(android.R.string.cancel, null)
.setView(view))
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_custom_rules, container, false)
......@@ -392,10 +432,9 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_manual_settings -> {
val (templateSelector, editText, dialog) = createAclRuleDialog()
dialog.setPositiveButton(android.R.string.ok, { _, _ ->
adapter.addFromTemplate(templateSelector.selectedItemPosition, editText.text.toString())
}).create().show()
val dialog = AclRuleDialog()
dialog.builder.setPositiveButton(android.R.string.ok, { _, _ -> dialog.add() })
dialog.show()
true
}
R.id.action_import -> {
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
......@@ -8,8 +9,15 @@
android:layout_height="wrap_content"
android:labelFor="@+id/content"
android:entries="@array/acl_rule_templates"/>
<EditText android:id="@+id/content"
<android.support.design.widget.TextInputLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textMultiLine"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment