Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
com.ccwangluo.accelerator
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sheteng
com.ccwangluo.accelerator
Commits
f0141317
Unverified
Commit
f0141317
authored
Feb 21, 2019
by
Max Lv
Committed by
GitHub
Feb 21, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2123 from shadowsocks/plugin-1.2.0
Plugin library 1.2.0
parents
1c1a8ed9
f4621f3f
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
257 additions
and
124 deletions
+257
-124
core/build.gradle
core/build.gradle
+5
-0
core/src/main/java/com/github/shadowsocks/database/Profile.kt
.../src/main/java/com/github/shadowsocks/database/Profile.kt
+27
-24
mobile/build.gradle
mobile/build.gradle
+5
-0
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
+18
-11
mobile/src/main/java/com/github/shadowsocks/ProfileConfigActivity.kt
...main/java/com/github/shadowsocks/ProfileConfigActivity.kt
+14
-7
mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt
...main/java/com/github/shadowsocks/ProfileConfigFragment.kt
+34
-22
mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
...in/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
+74
-58
plugin/CHANGES.md
plugin/CHANGES.md
+3
-0
plugin/build.gradle
plugin/build.gradle
+7
-2
plugin/src/main/java/com/github/shadowsocks/plugin/AlertDialogFragment.kt
...java/com/github/shadowsocks/plugin/AlertDialogFragment.kt
+70
-0
No files found.
core/build.gradle
View file @
f0141317
apply
plugin:
'com.android.library'
apply
plugin:
'kotlin-android'
apply
plugin:
'kotlin-android-extensions'
apply
plugin:
'kotlin-kapt'
android
{
...
...
@@ -38,6 +39,10 @@ android {
}
}
androidExtensions
{
experimental
=
true
}
def
lifecycleVersion
=
'2.0.0'
def
roomVersion
=
'2.0.0'
def
workVersion
=
'1.0.0-rc01'
...
...
core/src/main/java/com/github/shadowsocks/database/Profile.kt
View file @
f0141317
...
...
@@ -21,6 +21,7 @@
package
com.github.shadowsocks.database
import
android.net.Uri
import
android.os.Parcelable
import
android.util.Base64
import
android.util.Log
import
android.util.LongSparseArray
...
...
@@ -32,6 +33,7 @@ import com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.asIterable
import
com.github.shadowsocks.utils.parsePort
import
kotlinx.android.parcel.Parcelize
import
org.json.JSONArray
import
org.json.JSONObject
import
org.json.JSONTokener
...
...
@@ -41,7 +43,31 @@ import java.net.URISyntaxException
import
java.util.*
@Entity
class
Profile
:
Serializable
{
@Parcelize
data class
Profile
(
@PrimaryKey
(
autoGenerate
=
true
)
var
id
:
Long
=
0
,
var
name
:
String
?
=
""
,
var
host
:
String
=
"198.199.101.152"
,
var
remotePort
:
Int
=
8388
,
var
password
:
String
=
"u1rRWTssNv0p"
,
var
method
:
String
=
"aes-256-cfb"
,
var
route
:
String
=
"all"
,
var
remoteDns
:
String
=
"8.8.8.8"
,
var
proxyApps
:
Boolean
=
false
,
var
bypass
:
Boolean
=
false
,
var
udpdns
:
Boolean
=
false
,
var
ipv6
:
Boolean
=
true
,
var
individual
:
String
=
""
,
var
tx
:
Long
=
0
,
var
rx
:
Long
=
0
,
var
userOrder
:
Long
=
0
,
var
plugin
:
String
?
=
null
,
var
udpFallback
:
Long
?
=
null
,
@Ignore
// not persisted in db, only used by direct boot
var
dirty
:
Boolean
=
false
)
:
Parcelable
,
Serializable
{
companion
object
{
private
const
val
TAG
=
"ShadowParser"
private
const
val
serialVersionUID
=
0L
...
...
@@ -200,29 +226,6 @@ class Profile : Serializable {
fun
deleteAll
():
Int
}
@PrimaryKey
(
autoGenerate
=
true
)
var
id
:
Long
=
0
var
name
:
String
?
=
""
var
host
:
String
=
"198.199.101.152"
var
remotePort
:
Int
=
8388
var
password
:
String
=
"u1rRWTssNv0p"
var
method
:
String
=
"aes-256-cfb"
var
route
:
String
=
"all"
var
remoteDns
:
String
=
"8.8.8.8"
var
proxyApps
:
Boolean
=
false
var
bypass
:
Boolean
=
false
var
udpdns
:
Boolean
=
false
var
ipv6
:
Boolean
=
true
var
individual
:
String
=
""
var
tx
:
Long
=
0
var
rx
:
Long
=
0
var
userOrder
:
Long
=
0
var
plugin
:
String
?
=
null
var
udpFallback
:
Long
?
=
null
@Ignore
// not persisted in db, only used by direct boot
var
dirty
:
Boolean
=
false
val
formattedAddress
get
()
=
(
if
(
host
.
contains
(
":"
))
"[%s]:%d"
else
"%s:%d"
).
format
(
host
,
remotePort
)
val
formattedName
get
()
=
if
(
name
.
isNullOrEmpty
())
formattedAddress
else
name
!!
...
...
mobile/build.gradle
View file @
f0141317
...
...
@@ -5,6 +5,7 @@ import java.util.regex.Pattern
apply
plugin:
'com.android.application'
apply
plugin:
'io.fabric'
apply
plugin:
'kotlin-android'
apply
plugin:
'kotlin-android-extensions'
def
getCurrentFlavor
()
{
String
task
=
getGradle
().
getStartParameter
().
getTaskRequests
().
toString
()
...
...
@@ -49,6 +50,10 @@ android {
new
File
(
project
(
':core'
).
buildDir
,
"intermediates/bundles/${getCurrentFlavor()}/jni"
)
}
androidExtensions
{
experimental
=
true
}
dependencies
{
implementation
project
(
':core'
)
implementation
"androidx.browser:browser:1.0.0"
...
...
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
View file @
f0141317
...
...
@@ -23,6 +23,7 @@ package com.github.shadowsocks
import
android.app.Activity
import
android.app.backup.BackupManager
import
android.content.ActivityNotFoundException
import
android.content.DialogInterface
import
android.content.Intent
import
android.net.VpnService
import
android.nfc.NdefMessage
...
...
@@ -30,6 +31,7 @@ import android.nfc.NfcAdapter
import
android.os.Bundle
import
android.os.DeadObjectException
import
android.os.Handler
import
android.os.Parcelable
import
android.util.Log
import
android.view.KeyCharacterMap
import
android.view.KeyEvent
...
...
@@ -51,6 +53,8 @@ import com.github.shadowsocks.aidl.TrafficStats
import
com.github.shadowsocks.bg.BaseService
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.plugin.AlertDialogFragment
import
com.github.shadowsocks.plugin.Empty
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import
com.github.shadowsocks.utils.Key
...
...
@@ -58,6 +62,7 @@ import com.github.shadowsocks.widget.ServiceButton
import
com.github.shadowsocks.widget.StatsBar
import
com.google.android.material.navigation.NavigationView
import
com.google.android.material.snackbar.Snackbar
import
kotlinx.android.parcel.Parcelize
class
MainActivity
:
AppCompatActivity
(),
ShadowsocksConnection
.
Callback
,
OnPreferenceDataStoreChangeListener
,
NavigationView
.
OnNavigationItemSelectedListener
{
...
...
@@ -68,6 +73,17 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
var
stateListener
:
((
Int
)
->
Unit
)?
=
null
}
@Parcelize
data class
ProfilesArg
(
val
profiles
:
List
<
Profile
>)
:
Parcelable
class
ImportProfilesDialogFragment
:
AlertDialogFragment
<
ProfilesArg
,
Empty
>()
{
override
fun
AlertDialog
.
Builder
.
prepare
(
listener
:
DialogInterface
.
OnClickListener
)
{
setTitle
(
R
.
string
.
add_profile_dialog
)
setPositiveButton
(
R
.
string
.
yes
)
{
_
,
_
->
arg
.
profiles
.
forEach
{
ProfileManager
.
createProfile
(
it
)
}
}
setNegativeButton
(
R
.
string
.
no
,
null
)
setMessage
(
arg
.
profiles
.
joinToString
(
"\n"
))
}
}
// UI
private
lateinit
var
fab
:
ServiceButton
private
lateinit
var
stats
:
StatsBar
...
...
@@ -188,17 +204,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
}
if
(
sharedStr
.
isNullOrEmpty
())
return
val
profiles
=
Profile
.
findAllUrls
(
sharedStr
,
Core
.
currentProfile
?.
first
).
toList
()
if
(
profiles
.
isEmpty
())
{
snackbar
().
setText
(
R
.
string
.
profile_invalid_input
).
show
()
return
}
AlertDialog
.
Builder
(
this
)
.
setTitle
(
R
.
string
.
add_profile_dialog
)
.
setPositiveButton
(
R
.
string
.
yes
)
{
_
,
_
->
profiles
.
forEach
{
ProfileManager
.
createProfile
(
it
)
}
}
.
setNegativeButton
(
R
.
string
.
no
,
null
)
.
setMessage
(
profiles
.
joinToString
(
"\n"
))
.
create
()
.
show
()
if
(
profiles
.
isEmpty
())
snackbar
().
setText
(
R
.
string
.
profile_invalid_input
).
show
()
else
ImportProfilesDialogFragment
().
withArg
(
ProfilesArg
(
profiles
)).
show
(
supportFragmentManager
,
null
)
}
override
fun
onPreferenceDataStoreChanged
(
store
:
PreferenceDataStore
,
key
:
String
?)
{
...
...
mobile/src/main/java/com/github/shadowsocks/ProfileConfigActivity.kt
View file @
f0141317
...
...
@@ -21,12 +21,15 @@
package
com.github.shadowsocks
import
android.app.Activity
import
android.content.DialogInterface
import
android.content.Intent
import
android.os.Bundle
import
android.view.Menu
import
android.view.MenuItem
import
androidx.appcompat.app.AlertDialog
import
androidx.appcompat.app.AppCompatActivity
import
com.github.shadowsocks.plugin.AlertDialogFragment
import
com.github.shadowsocks.plugin.Empty
import
com.github.shadowsocks.plugin.PluginContract
import
com.github.shadowsocks.preference.DataStore
...
...
@@ -35,6 +38,15 @@ class ProfileConfigActivity : AppCompatActivity() {
const
val
REQUEST_CODE_PLUGIN_HELP
=
1
}
class
UnsavedChangesDialogFragment
:
AlertDialogFragment
<
Empty
,
Empty
>()
{
override
fun
AlertDialog
.
Builder
.
prepare
(
listener
:
DialogInterface
.
OnClickListener
)
{
setTitle
(
R
.
string
.
unsaved_changes_prompt
)
setPositiveButton
(
R
.
string
.
yes
,
listener
)
setNegativeButton
(
R
.
string
.
no
,
listener
)
setNeutralButton
(
android
.
R
.
string
.
cancel
,
null
)
}
}
private
val
child
by
lazy
{
supportFragmentManager
.
findFragmentById
(
R
.
id
.
content
)
as
ProfileConfigFragment
}
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
...
...
@@ -59,13 +71,8 @@ class ProfileConfigActivity : AppCompatActivity() {
override
fun
onOptionsItemSelected
(
item
:
MenuItem
)
=
child
.
onOptionsItemSelected
(
item
)
override
fun
onBackPressed
()
{
if
(
DataStore
.
dirty
)
AlertDialog
.
Builder
(
this
)
.
setTitle
(
R
.
string
.
unsaved_changes_prompt
)
.
setPositiveButton
(
R
.
string
.
yes
)
{
_
,
_
->
child
.
saveAndExit
()
}
.
setNegativeButton
(
R
.
string
.
no
)
{
_
,
_
->
finish
()
}
.
setNeutralButton
(
android
.
R
.
string
.
cancel
,
null
)
.
create
()
.
show
()
else
super
.
onBackPressed
()
if
(
DataStore
.
dirty
)
UnsavedChangesDialogFragment
().
show
(
child
,
ProfileConfigFragment
.
REQUEST_UNSAVED_CHANGES
)
else
super
.
onBackPressed
()
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
...
...
mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt
View file @
f0141317
...
...
@@ -22,8 +22,10 @@ package com.github.shadowsocks
import
android.app.Activity
import
android.content.BroadcastReceiver
import
android.content.DialogInterface
import
android.content.Intent
import
android.os.Bundle
import
android.os.Parcelable
import
android.view.MenuItem
import
androidx.appcompat.app.AlertDialog
import
androidx.core.os.bundleOf
...
...
@@ -33,10 +35,7 @@ import androidx.preference.SwitchPreference
import
com.github.shadowsocks.Core.app
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginContract
import
com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.plugin.PluginOptions
import
com.github.shadowsocks.plugin.*
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.IconListPreference
import
com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
...
...
@@ -47,11 +46,26 @@ import com.github.shadowsocks.utils.Key
import
com.google.android.material.snackbar.Snackbar
import
com.takisoft.preferencex.EditTextPreference
import
com.takisoft.preferencex.PreferenceFragmentCompat
import
kotlinx.android.parcel.Parcelize
class
ProfileConfigFragment
:
PreferenceFragmentCompat
(),
Preference
.
OnPreferenceChangeListener
,
OnPreferenceDataStoreChangeListener
{
companion
object
{
private
const
val
REQUEST_CODE_PLUGIN_CONFIGURE
=
1
const
val
REQUEST_UNSAVED_CHANGES
=
2
}
@Parcelize
data class
ProfileIdArg
(
val
profileId
:
Long
)
:
Parcelable
class
DeleteConfirmationDialogFragment
:
AlertDialogFragment
<
ProfileIdArg
,
Empty
>()
{
override
fun
AlertDialog
.
Builder
.
prepare
(
listener
:
DialogInterface
.
OnClickListener
)
{
setTitle
(
R
.
string
.
delete_confirm_prompt
)
setPositiveButton
(
R
.
string
.
yes
)
{
_
,
_
->
ProfileManager
.
delProfile
(
arg
.
profileId
)
requireActivity
().
finish
()
}
setNegativeButton
(
R
.
string
.
no
,
null
)
}
}
private
var
profileId
=
-
1L
...
...
@@ -116,7 +130,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
bundleOf
(
Pair
(
"key"
,
Key
.
pluginConfigure
),
Pair
(
PluginConfigurationDialogFragment
.
PLUGIN_ID_FRAGMENT_TAG
,
pluginConfiguration
.
selected
)))
fun
saveAndExit
()
{
private
fun
saveAndExit
()
{
val
profile
=
ProfileManager
.
getProfile
(
profileId
)
?:
Profile
()
profile
.
id
=
profileId
profile
.
deserialize
()
...
...
@@ -161,28 +175,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
if
(
requestCode
==
REQUEST_CODE_PLUGIN_CONFIGURE
)
when
(
resultCode
)
{
when
(
requestCode
)
{
REQUEST_CODE_PLUGIN_CONFIGURE
->
when
(
resultCode
)
{
Activity
.
RESULT_OK
->
{
val
options
=
data
?.
getStringExtra
(
PluginContract
.
EXTRA_OPTIONS
)
pluginConfigure
.
text
=
options
onPreferenceChange
(
null
,
options
)
}
PluginContract
.
RESULT_FALLBACK
->
showPluginEditor
()
}
else
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
}
REQUEST_UNSAVED_CHANGES
->
when
(
resultCode
)
{
DialogInterface
.
BUTTON_POSITIVE
->
saveAndExit
()
DialogInterface
.
BUTTON_NEGATIVE
->
requireActivity
().
finish
()
}
else
->
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
}
}
override
fun
onOptionsItemSelected
(
item
:
MenuItem
)
=
when
(
item
.
itemId
)
{
R
.
id
.
action_delete
->
{
val
activity
=
requireActivity
()
AlertDialog
.
Builder
(
activity
)
.
setTitle
(
R
.
string
.
delete_confirm_prompt
)
.
setPositiveButton
(
R
.
string
.
yes
)
{
_
,
_
->
ProfileManager
.
delProfile
(
profileId
)
activity
.
finish
()
}
.
setNegativeButton
(
R
.
string
.
no
,
null
)
.
create
()
.
show
()
DeleteConfirmationDialogFragment
().
withArg
(
ProfileIdArg
(
profileId
)).
show
(
this
)
true
}
R
.
id
.
action_apply
->
{
...
...
mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
View file @
f0141317
...
...
@@ -22,9 +22,12 @@ package com.github.shadowsocks.acl
import
android.content.ClipData
import
android.content.ClipboardManager
import
android.content.DialogInterface
import
android.content.Intent
import
android.content.res.Configuration
import
android.os.Build
import
android.os.Bundle
import
android.os.Parcelable
import
android.text.Editable
import
android.text.TextWatcher
import
android.view.*
...
...
@@ -43,10 +46,12 @@ import com.github.shadowsocks.R
import
com.github.shadowsocks.ToolbarFragment
import
com.github.shadowsocks.bg.BaseService
import
com.github.shadowsocks.net.Subnet
import
com.github.shadowsocks.plugin.AlertDialogFragment
import
com.github.shadowsocks.utils.asIterable
import
com.github.shadowsocks.utils.resolveResourceId
import
com.github.shadowsocks.widget.UndoSnackbarManager
import
com.google.android.material.textfield.TextInputLayout
import
kotlinx.android.parcel.Parcelize
import
java.net.IDN
import
java.net.MalformedURLException
import
java.net.URL
...
...
@@ -55,6 +60,8 @@ import java.util.regex.PatternSyntaxException
class
CustomRulesFragment
:
ToolbarFragment
(),
Toolbar
.
OnMenuItemClickListener
,
ActionMode
.
Callback
{
companion
object
{
private
const
val
REQUEST_CODE_ADD
=
1
private
const
val
REQUEST_CODE_EDIT
=
2
private
const
val
TEMPLATE_REGEX_DOMAIN
=
"(^|\\.)%s$"
private
const
val
SELECTED_SUBNETS
=
"com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_SUBNETS"
...
...
@@ -63,6 +70,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
// unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$)
private
val
domainPattern
=
"(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)"
.
toRegex
()
@Suppress
(
"FunctionName"
)
private
fun
AclItem
(
item
:
Any
)
=
when
(
item
)
{
is
String
->
AclItem
(
item
,
false
)
is
Subnet
->
AclItem
(
item
.
toString
(),
false
)
is
URL
->
AclItem
(
item
.
toString
(),
true
)
else
->
throw
IllegalArgumentException
(
"item"
)
}
}
private
enum
class
Template
{
...
...
@@ -70,53 +85,50 @@ 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
@Parcelize
data class
AclItem
(
val
item
:
String
=
""
,
val
isUrl
:
Boolean
=
false
)
:
Parcelable
{
fun
toAny
()
=
if
(
isUrl
)
URL
(
item
)
else
Subnet
.
fromString
(
item
)
?:
item
}
@Parcelize
data class
AclEditResult
(
val
edited
:
AclItem
,
val
replacing
:
AclItem
)
:
Parcelable
class
AclRuleDialogFragment
:
AlertDialogFragment
<
AclItem
,
AclEditResult
>(),
TextWatcher
,
AdapterView
.
OnItemSelectedListener
{
private
lateinit
var
templateSelector
:
Spinner
private
lateinit
var
editText
:
EditText
private
lateinit
var
inputLayout
:
TextInputLayout
private
lateinit
var
positive
:
Button
init
{
override
fun
AlertDialog
.
Builder
.
prepare
(
listener
:
DialogInterface
.
OnClickListener
)
{
val
activity
=
requireActivity
()
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
=
domainPattern
.
find
(
item
)
templateSelector
.
setSelection
(
Template
.
Generic
.
ordinal
)
editText
.
setText
(
arg
.
item
)
when
{
arg
.
isUrl
->
templateSelector
.
setSelection
(
Template
.
Url
.
ordinal
)
Subnet
.
fromString
(
arg
.
item
)
==
null
->
{
val
match
=
domainPattern
.
find
(
arg
.
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
)
templateSelector
.
onItemSelectedListener
=
this
@AclRuleDialogFragment
editText
.
addTextChangedListener
(
this
@AclRuleDialogFragment
)
setTitle
(
R
.
string
.
edit_rule
)
setPositiveButton
(
android
.
R
.
string
.
ok
,
listener
)
setNegativeButton
(
android
.
R
.
string
.
cancel
,
null
)
if
(
arg
.
item
.
isNotEmpty
())
setNeutralButton
(
R
.
string
.
delete
,
listener
)
setView
(
view
)
}
fun
show
()
{
dialog
=
builder
.
create
()
dialog
.
show
()
positive
=
dialog
.
getButton
(
AlertDialog
.
BUTTON_POSITIVE
)
override
fun
onStart
()
{
super
.
onStart
()
positive
=
(
dialog
as
AlertDialog
).
getButton
(
AlertDialog
.
BUTTON_POSITIVE
)
validate
()
}
...
...
@@ -157,15 +169,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
inputLayout
.
error
=
message
}
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
,
override
val
ret
get
()
=
AclEditResult
(
editText
.
text
.
toString
().
let
{
text
->
when
(
Template
.
values
()[
templateSelector
.
selectedItemPosition
])
{
Template
.
Generic
->
AclItem
(
text
)
Template
.
Domain
->
AclItem
(
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
))
}
Template
.
Url
->
AclItem
(
text
,
true
)
}
},
arg
)
}
private
inner
class
AclRuleViewHolder
(
view
:
View
)
:
RecyclerView
.
ViewHolder
(
view
),
...
...
@@ -197,22 +208,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
}
override
fun
onClick
(
v
:
View
?)
{
if
(
selectedItems
.
isNotEmpty
())
onLongClick
(
v
)
else
{
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
=
dialog
.
add
()
?:
adapter
.
add
(
item
)
if
(
index
!=
null
)
list
.
post
{
list
.
scrollToPosition
(
index
)
}
}
dialog
.
show
()
if
(
selectedItems
.
isNotEmpty
())
onLongClick
(
v
)
else
AclRuleDialogFragment
().
withArg
(
AclItem
(
item
)).
show
(
this
@CustomRulesFragment
,
REQUEST_CODE_EDIT
)
}
}
override
fun
onLongClick
(
p0
:
View
?):
Boolean
{
override
fun
onLongClick
(
v
:
View
?):
Boolean
{
if
(!
selectedItems
.
add
(
item
))
selectedItems
.
remove
(
item
)
// toggle
onSelectedItemsUpdated
()
itemView
.
isSelected
=
!
itemView
.
isSelected
...
...
@@ -427,9 +426,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
override
fun
onMenuItemClick
(
item
:
MenuItem
):
Boolean
=
when
(
item
.
itemId
)
{
R
.
id
.
action_manual_settings
->
{
val
dialog
=
AclRuleDialog
()
dialog
.
builder
.
setPositiveButton
(
android
.
R
.
string
.
ok
)
{
_
,
_
->
dialog
.
add
()
}
dialog
.
show
()
AclRuleDialogFragment
().
withArg
(
AclItem
()).
show
(
this
,
REQUEST_CODE_ADD
)
true
}
R
.
id
.
action_import_clipboard
->
{
...
...
@@ -451,6 +448,25 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
else
->
false
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
val
editing
=
when
(
requestCode
)
{
REQUEST_CODE_ADD
->
false
REQUEST_CODE_EDIT
->
true
else
->
return
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
}
val
ret
by
lazy
{
AlertDialogFragment
.
getRet
<
AclEditResult
>(
data
!!
)
}
when
(
resultCode
)
{
DialogInterface
.
BUTTON_POSITIVE
->
{
if
(
editing
)
adapter
.
remove
(
ret
.
replacing
.
toAny
())
adapter
.
add
(
ret
.
edited
.
toAny
())
?.
also
{
list
.
post
{
list
.
scrollToPosition
(
it
)
}
}
}
DialogInterface
.
BUTTON_NEUTRAL
->
ret
.
replacing
.
toAny
().
let
{
item
->
adapter
.
remove
(
item
)
undoManager
.
remove
(
Pair
(-
1
,
item
))
}
}
}
override
fun
onDetach
()
{
undoManager
.
flush
()
mode
?.
finish
()
...
...
plugin/CHANGES.md
View file @
f0141317
*
1.2.0:
*
New helper class
`AlertDialogFragment`
for creating
`AlertDialog`
that persists through configuration changes;
*
Dependency update:
`com.google.android.material:material:1.1.0-alpha03`
.
*
1.1.0:
*
Having control characters in plugin options is no longer allowed.
If this breaks your plugin, you are doing it wrong.
...
...
plugin/build.gradle
View file @
f0141317
apply
plugin:
'com.android.library'
apply
plugin:
'kotlin-android'
apply
plugin:
'kotlin-android-extensions'
apply
from:
'gradle-mvn-push.gradle'
...
...
@@ -10,8 +11,8 @@ android {
defaultConfig
{
minSdkVersion
rootProject
.
minSdkVersion
targetSdkVersion
rootProject
.
sdkVersion
versionCode
8
versionName
"1.
1
.0"
versionCode
9
versionName
"1.
2
.0"
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
}
...
...
@@ -23,6 +24,10 @@ android {
}
}
androidExtensions
{
experimental
=
true
}
dependencies
{
api
"androidx.core:core-ktx:1.0.1"
api
"com.google.android.material:material:1.1.0-alpha03"
...
...
plugin/src/main/java/com/github/shadowsocks/plugin/AlertDialogFragment.kt
0 → 100644
View file @
f0141317
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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
com.github.shadowsocks.plugin
import
android.app.Activity
import
android.content.DialogInterface
import
android.content.Intent
import
android.os.Bundle
import
android.os.Parcelable
import
androidx.appcompat.app.AlertDialog
import
androidx.appcompat.app.AppCompatDialogFragment
import
androidx.fragment.app.Fragment
import
kotlinx.android.parcel.Parcelize
/**
* Based on: https://android.googlesource.com/platform/packages/apps/ExactCalculator/+/8c43f06/src/com/android/calculator2/AlertDialogFragment.java
*/
abstract
class
AlertDialogFragment
<
Arg
:
Parcelable
,
Ret
:
Parcelable
>
:
AppCompatDialogFragment
(),
DialogInterface
.
OnClickListener
{
companion
object
{
private
const
val
KEY_ARG
=
"arg"
private
const
val
KEY_RET
=
"ret"
fun
<
T
:
Parcelable
>
getRet
(
data
:
Intent
)
=
data
.
extras
!!
.
getParcelable
<
T
>(
KEY_RET
)
!!
}
protected
abstract
fun
AlertDialog
.
Builder
.
prepare
(
listener
:
DialogInterface
.
OnClickListener
)
protected
val
arg
by
lazy
{
arguments
!!
.
getParcelable
<
Arg
>(
KEY_ARG
)
!!
}
protected
open
val
ret
:
Ret
?
get
()
=
null
fun
withArg
(
arg
:
Arg
)
=
apply
{
arguments
=
Bundle
().
apply
{
putParcelable
(
KEY_ARG
,
arg
)
}
}
override
fun
onCreateDialog
(
savedInstanceState
:
Bundle
?):
AlertDialog
=
AlertDialog
.
Builder
(
requireContext
()).
also
{
it
.
prepare
(
this
)
}.
create
()
override
fun
onClick
(
dialog
:
DialogInterface
?,
which
:
Int
)
{
targetFragment
?.
onActivityResult
(
targetRequestCode
,
which
,
ret
?.
let
{
Intent
().
replaceExtras
(
Bundle
().
apply
{
putParcelable
(
KEY_RET
,
it
)
})
})
}
override
fun
onDismiss
(
dialog
:
DialogInterface
)
{
super
.
onDismiss
(
dialog
)
targetFragment
?.
onActivityResult
(
targetRequestCode
,
Activity
.
RESULT_CANCELED
,
null
)
}
fun
show
(
target
:
Fragment
,
requestCode
:
Int
=
0
,
tag
:
String
=
javaClass
.
simpleName
)
{
setTargetFragment
(
target
,
requestCode
)
show
(
target
.
fragmentManager
?:
return
,
tag
)
}
}
@Parcelize
class
Empty
:
Parcelable
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment