Initial draft for porting this project to Gradle + Kotlin
This is one of my stupidest idea ever.
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 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.preference
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.support.design.widget.BottomSheetDialog
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.github.shadowsocks.R
|
||||
|
||||
class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
|
||||
private inner class IconListViewHolder(val dialog: BottomSheetDialog, view: View) : RecyclerView.ViewHolder(view),
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
private var index = 0
|
||||
private val text1 = view.findViewById<TextView>(android.R.id.text1)
|
||||
private val text2 = view.findViewById<TextView>(android.R.id.text2)
|
||||
private val icon = view.findViewById<ImageView>(android.R.id.icon)
|
||||
|
||||
init {
|
||||
view.setOnClickListener(this)
|
||||
view.setOnLongClickListener(this)
|
||||
}
|
||||
|
||||
fun bind(i: Int, selected: Boolean = false) {
|
||||
text1.text = preference.entries[i]
|
||||
text2.text = preference.entryValues[i]
|
||||
val typeface = if (selected) Typeface.BOLD else Typeface.NORMAL
|
||||
text1.setTypeface(null, typeface)
|
||||
text2.setTypeface(null, typeface)
|
||||
text2.visibility = if (preference.entryValues[i].isNotEmpty() &&
|
||||
preference.entries[i] != preference.entryValues[i]) View.VISIBLE else View.GONE
|
||||
icon.setImageDrawable(preference.entryIcons?.get(i))
|
||||
index = i
|
||||
}
|
||||
|
||||
override fun onClick(p0: View?) {
|
||||
clickedIndex = index
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
override fun onLongClick(p0: View?): Boolean {
|
||||
val pn = preference.entryPackageNames?.get(index) ?: return false
|
||||
return try {
|
||||
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.Builder()
|
||||
.scheme("package")
|
||||
.opaquePart(pn)
|
||||
.build()))
|
||||
true
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private inner class IconListAdapter(private val dialog: BottomSheetDialog) :
|
||||
RecyclerView.Adapter<IconListViewHolder>() {
|
||||
override fun getItemCount(): Int = preference.entries.size
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = IconListViewHolder(dialog,
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.icon_list_item_2, parent, false))
|
||||
override fun onBindViewHolder(holder: IconListViewHolder, position: Int) {
|
||||
if (preference.selectedEntry < 0) holder.bind(position) else when (position) {
|
||||
0 -> holder.bind(preference.selectedEntry, true)
|
||||
in preference.selectedEntry + 1 .. Int.MAX_VALUE -> holder.bind(position)
|
||||
else -> holder.bind(position - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val preference by lazy { getPreference() as IconListPreference }
|
||||
private var clickedIndex = -1
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val activity = activity
|
||||
val dialog = BottomSheetDialog(activity!!, theme)
|
||||
val recycler = RecyclerView(activity)
|
||||
recycler.setHasFixedSize(true)
|
||||
recycler.layoutManager = LinearLayoutManager(activity)
|
||||
recycler.adapter = IconListAdapter(dialog)
|
||||
recycler.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
dialog.setContentView(recycler)
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
if (clickedIndex >= 0 && clickedIndex != preference.selectedEntry) {
|
||||
val value = preference.entryValues[clickedIndex].toString()
|
||||
if (preference.callChangeListener(value)) preference.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 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.preference
|
||||
|
||||
import android.os.Binder
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
import com.github.shadowsocks.database.DBHelper
|
||||
import com.github.shadowsocks.database.KeyValuePair
|
||||
import com.github.shadowsocks.utils.Key
|
||||
import com.github.shadowsocks.utils.parsePort
|
||||
import java.util.*
|
||||
|
||||
object DataStore : PreferenceDataStore() {
|
||||
fun getBoolean(key: String?) = DBHelper.kvPairDao.queryForId(key)?.boolean
|
||||
fun getFloat(key: String?) = DBHelper.kvPairDao.queryForId(key)?.float
|
||||
fun getInt(key: String?) = DBHelper.kvPairDao.queryForId(key)?.int
|
||||
fun getLong(key: String?) = DBHelper.kvPairDao.queryForId(key)?.long
|
||||
fun getString(key: String?) = DBHelper.kvPairDao.queryForId(key)?.string
|
||||
fun getStringSet(key: String?) = DBHelper.kvPairDao.queryForId(key)?.stringSet
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
|
||||
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
|
||||
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue
|
||||
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue
|
||||
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue
|
||||
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(key) ?: defValue
|
||||
|
||||
fun putBoolean(key: String?, value: Boolean?) = if (value == null) remove(key) else putBoolean(key, value)
|
||||
fun putFloat(key: String?, value: Float?) = if (value == null) remove(key) else putFloat(key, value)
|
||||
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
|
||||
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
fun remove(key: String?) {
|
||||
DBHelper.kvPairDao.deleteById(key)
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
|
||||
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
|
||||
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
|
||||
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
|
||||
|
||||
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
|
||||
private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() }
|
||||
private fun getLocalPort(key: String, default: Int): Int {
|
||||
val value = getInt(key)
|
||||
return if (value != null) {
|
||||
putString(key, value.toString())
|
||||
value
|
||||
} else parsePort(getString(key), default + userIndex)
|
||||
}
|
||||
|
||||
var profileId: Int
|
||||
get() = getInt(Key.id) ?: 0
|
||||
set(value) = putInt(Key.id, value)
|
||||
var serviceMode: String
|
||||
get() = getString(Key.serviceMode) ?: Key.modeVpn
|
||||
set(value) = putString(Key.serviceMode, value)
|
||||
var portProxy: Int
|
||||
get() = getLocalPort(Key.portProxy, 1080)
|
||||
set(value) = putString(Key.portProxy, value.toString())
|
||||
var portLocalDns: Int
|
||||
get() = getLocalPort(Key.portLocalDns, 5450)
|
||||
set(value) = putString(Key.portLocalDns, value.toString())
|
||||
var portTransproxy: Int
|
||||
get() = getLocalPort(Key.portTransproxy, 8200)
|
||||
set(value) = putString(Key.portTransproxy, value.toString())
|
||||
|
||||
var proxyApps: Boolean
|
||||
get() = getBoolean(Key.proxyApps) ?: false
|
||||
set(value) = putBoolean(Key.proxyApps, value)
|
||||
var bypass: Boolean
|
||||
get() = getBoolean(Key.bypass) ?: false
|
||||
set(value) = putBoolean(Key.bypass, value)
|
||||
var individual: String
|
||||
get() = getString(Key.individual) ?: ""
|
||||
set(value) = putString(Key.individual, value)
|
||||
var plugin: String
|
||||
get() = getString(Key.plugin) ?: ""
|
||||
set(value) = putString(Key.plugin, value)
|
||||
var dirty: Boolean
|
||||
get() = getBoolean(Key.dirty) ?: false
|
||||
set(value) = putBoolean(Key.dirty, value)
|
||||
|
||||
fun initGlobal() {
|
||||
// temporary workaround for support lib bug
|
||||
if (getString(Key.serviceMode) == null) serviceMode = serviceMode
|
||||
if (getString(Key.portProxy) == null) portProxy = portProxy
|
||||
if (getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
|
||||
if (getString(Key.portTransproxy) == null) portTransproxy = portTransproxy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 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.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.preference.Preference.OnPreferenceChangeListener
|
||||
import android.support.v7.preference.ListPreference
|
||||
import android.util.AttributeSet
|
||||
|
||||
class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) {
|
||||
var entryIcons: Array<Drawable?>? = null
|
||||
val selectedEntry: Int get() = entryValues.indexOf(value)
|
||||
val entryIcon: Drawable? get() = try {
|
||||
entryIcons?.get(selectedEntry)
|
||||
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
// fun setEntryIcons(@ArrayRes entryIconsResId: Int) {
|
||||
// val array = getContext().getResources().obtainTypedArray(entryIconsResId)
|
||||
// entryIcons = Array(array.length(), { i -> array.getDrawable(i) })
|
||||
// array.recycle()
|
||||
// }
|
||||
|
||||
var unknownValueSummary: String? = null
|
||||
var entryPackageNames: Array<String>? = null
|
||||
|
||||
var listener: OnPreferenceChangeListener? = null
|
||||
override fun getOnPreferenceChangeListener(): OnPreferenceChangeListener? = listener
|
||||
override fun setOnPreferenceChangeListener(listener: OnPreferenceChangeListener?) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
init {
|
||||
super.setOnPreferenceChangeListener(OnPreferenceChangeListener { preference, newValue ->
|
||||
val listener = listener
|
||||
if (listener == null || listener.onPreferenceChange(preference, newValue)) {
|
||||
setValue(newValue.toString())
|
||||
checkSummary()
|
||||
if (entryIcons != null) setIcon(entryIcon)
|
||||
true
|
||||
} else false
|
||||
})
|
||||
// val a = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference)
|
||||
// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1)
|
||||
// if (entryIconsResId != -1) entryIcons = entryIconsResId
|
||||
// a.recycle()
|
||||
}
|
||||
|
||||
fun checkSummary() {
|
||||
val unknownValueSummary = unknownValueSummary
|
||||
if (unknownValueSummary != null) summary = if (selectedEntry < 0) unknownValueSummary.format(value) else "%s"
|
||||
}
|
||||
|
||||
fun init() = setIcon(entryIcon)
|
||||
override fun onSetInitialValue(restoreValue: Boolean, defaultValue: Any?) {
|
||||
super.onSetInitialValue(restoreValue, defaultValue)
|
||||
init()
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 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.preference
|
||||
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
|
||||
interface OnPreferenceDataStoreChangeListener {
|
||||
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 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.preference
|
||||
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import com.github.shadowsocks.ProfileConfigActivity
|
||||
import com.github.shadowsocks.plugin.PluginContract
|
||||
import com.github.shadowsocks.plugin.PluginManager
|
||||
import com.takisoft.fix.support.v7.preference.EditTextPreferenceDialogFragmentCompat
|
||||
|
||||
class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat() {
|
||||
companion object {
|
||||
const val PLUGIN_ID_FRAGMENT_TAG =
|
||||
"com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
|
||||
}
|
||||
|
||||
private lateinit var editText: EditText
|
||||
|
||||
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
val intent = PluginManager.buildIntent(arguments!!.getString(PLUGIN_ID_FRAGMENT_TAG),
|
||||
PluginContract.ACTION_HELP)
|
||||
if (intent.resolveActivity(activity!!.packageManager) != null) builder.setNeutralButton("?", { _, _ ->
|
||||
activity!!.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.text.toString()),
|
||||
ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
editText = view.findViewById(android.R.id.edit)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user