Unverified Commit 8c33345a authored by Mygod's avatar Mygod Committed by GitHub

Use Gericop's preference library (#1471)

* New SimpleMenuPreference;
* More Oreo-like preference style;
* I'm tired of fixing all those bugs;
* Remove NumberPickerPreference which nobody likes.
parent 74917eac
...@@ -9,10 +9,9 @@ Cannot connect to server: ...@@ -9,10 +9,9 @@ Cannot connect to server:
Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data. Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data.
### UI tips ### How to create a widget and/or switch profile based on network connectivity?
* Tap the number to enter the port you wish to use; (if the keyboard doesn't pop up automatically for some reason) Use [Tasker](http://tasker.dinglisch.net/) integration.
* Use Tasker integration to create a desktop widget.
### How to add QR code from local gallery? ### How to add QR code from local gallery?
......
...@@ -20,10 +20,13 @@ lazy val commonSettings = Seq( ...@@ -20,10 +20,13 @@ lazy val commonSettings = Seq(
resConfigs := Seq("fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"), resConfigs := Seq("fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"),
resolvers += Resolver.jcenterRepo,
resolvers += Resolver.bintrayRepo("gericop", "maven"),
resolvers += "google" at "https://maven.google.com" resolvers += "google" at "https://maven.google.com"
) )
val supportLibsVersion = "27.0.2" val supportLibsVersion = "27.0.2"
val takisoftFixVersion = "27.0.2.0"
lazy val root = Project(id = "shadowsocks-android", base = file(".")) lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings) .settings(commonSettings)
.aggregate(plugin, mobile) .aggregate(plugin, mobile)
...@@ -34,7 +37,11 @@ run in Android := (run in (mobile, Android)).evaluated ...@@ -34,7 +37,11 @@ run in Android := (run in (mobile, Android)).evaluated
lazy val plugin = project lazy val plugin = project
.settings(commonSettings) .settings(commonSettings)
.settings( .settings(
libraryDependencies += "com.android.support" % "preference-v14" % supportLibsVersion libraryDependencies ++=
"com.android.support" % "preference-v14" % supportLibsVersion ::
"com.takisoft.fix" % "preference-v7" % takisoftFixVersion ::
"com.takisoft.fix" % "preference-v7-simplemenu" % takisoftFixVersion ::
Nil
) )
lazy val mobile = project lazy val mobile = project
......
package com.github.shadowsocks;
import android.app.Activity;
/**
* This helper class is used to solve Java-Scala interop problem. Use onAttach(Context) and remove this class
* when minSdkVersion >= 23.
*
* @author Mygod
*/
public class DialogFragment extends android.app.DialogFragment {
protected void superOnAttach(Activity activity) {
super.onAttach(activity);
}
}
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#7488A1</color> <color name="ic_launcher_background">#7488A1</color>
<color name="material_drawer_selected_text">@color/material_accent_200</color>
</resources> </resources>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Shadowsocks.Material" parent="MaterialDrawerTheme.Light.DarkToolbar">
<item name="material_drawer_selected_text">@color/material_accent_200</item>
</style>
<style name="StyledScrollerTextAppearance" parent="@android:style/TextAppearance"> <style name="StyledScrollerTextAppearance" parent="@android:style/TextAppearance">
<item name="android:textSize">24sp</item> <item name="android:textSize">24sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
......
...@@ -8,32 +8,32 @@ ...@@ -8,32 +8,32 @@
<SwitchPreference android:key="tcp_fastopen" <SwitchPreference android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary" android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/> android:title="TCP Fast Open"/>
<be.mygod.preference.PreferenceCategory <PreferenceCategory
android:title="@string/advanced"> android:title="@string/advanced">
<DropDownPreference <SimpleMenuPreference
android:key="serviceMode" android:key="serviceMode"
android:entries="@array/service_modes" android:entries="@array/service_modes"
android:entryValues="@array/service_mode_values" android:entryValues="@array/service_mode_values"
android:defaultValue="vpn" android:defaultValue="vpn"
android:summary="%s" android:summary="%s"
android:title="@string/service_mode"/> android:title="@string/service_mode"/>
<be.mygod.preference.NumberPickerPreference <AutoSummaryEditTextPreference
app:min="1025"
app:max="65535"
android:key="portProxy" android:key="portProxy"
android:summary="%d" android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_proxy"/> android:title="@string/port_proxy"/>
<be.mygod.preference.NumberPickerPreference <AutoSummaryEditTextPreference
app:min="1025"
app:max="65535"
android:key="portLocalDns" android:key="portLocalDns"
android:summary="%d" android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_local_dns"/> android:title="@string/port_local_dns"/>
<be.mygod.preference.NumberPickerPreference <AutoSummaryEditTextPreference
app:min="1025"
app:max="65535"
android:key="portTransproxy" android:key="portTransproxy"
android:summary="%d" android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_transproxy"/> android:title="@string/port_transproxy"/>
</be.mygod.preference.PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
...@@ -2,51 +2,52 @@ ...@@ -2,51 +2,52 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks"> xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<be.mygod.preference.EditTextPreference <AutoSummaryEditTextPreference
android:key="profileName" android:key="profileName"
android:summary="%s" app:pref_summaryHasText="%s"
android:title="@string/profile_name"/> android:title="@string/profile_name"/>
<be.mygod.preference.PreferenceCategory <PreferenceCategory
android:title="@string/proxy_cat"> android:title="@string/proxy_cat">
<be.mygod.preference.EditTextPreference <AutoSummaryEditTextPreference
android:key="proxy" android:key="proxy"
android:summary="%s" app:pref_summaryHasText="%s"
android:title="@string/proxy"/> android:title="@string/proxy"/>
<be.mygod.preference.NumberPickerPreference <AutoSummaryEditTextPreference
app:min="1"
app:max="65535"
android:key="remotePortNum" android:key="remotePortNum"
android:summary="%d" android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/remote_port"/> android:title="@string/remote_port"/>
<be.mygod.preference.EditTextPreference <AutoSummaryEditTextPreference
android:inputType="textPassword" android:inputType="textPassword"
app:pref_summaryPasswordSubstituteLength="0"
android:key="sitekey" android:key="sitekey"
android:summary="%s" app:pref_summaryHasText="%s"
android:title="@string/sitekey"/> android:title="@string/sitekey"/>
<DropDownPreference <SimpleMenuPreference
android:key="encMethod" android:key="encMethod"
android:entries="@array/enc_method_entry" android:entries="@array/enc_method_entry"
android:entryValues="@array/enc_method_value" android:entryValues="@array/enc_method_value"
android:summary="%s" android:summary="%s"
android:title="@string/enc_method"/> android:title="@string/enc_method"/>
</be.mygod.preference.PreferenceCategory> </PreferenceCategory>
<be.mygod.preference.PreferenceCategory <PreferenceCategory
android:title="@string/feature_cat"> android:title="@string/feature_cat">
<DropDownPreference <SimpleMenuPreference
android:key="route" android:key="route"
android:entries="@array/route_entry" android:entries="@array/route_entry"
android:entryValues="@array/route_value" android:entryValues="@array/route_value"
android:summary="%s" android:summary="%s"
android:title="@string/route_list"/> android:title="@string/route_list"/>
<be.mygod.preference.EditTextPreference <AutoSummaryEditTextPreference
android:key="remoteDns" android:key="remoteDns"
android:title="@string/remote_dns" android:title="@string/remote_dns"
android:summary="%s"/> app:pref_summaryHasText="%s"/>
<SwitchPreference <SwitchPreference
android:key="isIpv6" android:key="isIpv6"
android:summary="@string/ipv6_summary" android:summary="@string/ipv6_summary"
...@@ -60,9 +61,9 @@ ...@@ -60,9 +61,9 @@
android:summary="@string/udp_dns_summary" android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/> android:title="@string/udp_dns"/>
</be.mygod.preference.PreferenceCategory> </PreferenceCategory>
<be.mygod.preference.PreferenceCategory <PreferenceCategory
android:title="@string/plugin"> android:title="@string/plugin">
<com.github.shadowsocks.preference.IconListPreference <com.github.shadowsocks.preference.IconListPreference
...@@ -70,12 +71,12 @@ ...@@ -70,12 +71,12 @@
android:persistent="false" android:persistent="false"
android:summary="%s" android:summary="%s"
android:title="@string/plugin"/> android:title="@string/plugin"/>
<be.mygod.preference.EditTextPreference <AutoSummaryEditTextPreference
android:key="plugin.configure" android:key="plugin.configure"
android:persistent="false" android:persistent="false"
android:summary="%s" app:pref_summaryHasText="%s"
android:title="@string/plugin_configure"/> android:title="@string/plugin_configure"/>
</be.mygod.preference.PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
...@@ -24,13 +24,13 @@ import android.os.Bundle ...@@ -24,13 +24,13 @@ import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import be.mygod.preference.PreferenceFragment
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.ServiceState import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.utils.{Key, TcpFastOpen} import com.github.shadowsocks.utils.{Key, TcpFastOpen}
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
class GlobalConfigFragment extends PreferenceFragment { class GlobalConfigFragment extends PreferenceFragmentCompatDividers {
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferencesFix(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore) getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
......
...@@ -33,7 +33,7 @@ import android.os.{Bundle, Handler} ...@@ -33,7 +33,7 @@ import android.os.{Bundle, Handler}
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.{FloatingActionButton, Snackbar} import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.content.res.AppCompatResources import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
...@@ -69,7 +69,7 @@ object MainActivity { ...@@ -69,7 +69,7 @@ object MainActivity {
var stateListener: Int => Unit = _ var stateListener: Int => Unit = _
} }
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener class MainActivity extends AppCompatActivity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
with OnPreferenceDataStoreChangeListener { with OnPreferenceDataStoreChangeListener {
import MainActivity._ import MainActivity._
...@@ -152,8 +152,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -152,8 +152,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
rxText.setText(TrafficMonitor.formatTraffic(rxTotal)) rxText.setText(TrafficMonitor.formatTraffic(rxTotal))
txRateText.setText(TrafficMonitor.formatTraffic(txRate) + "/s") txRateText.setText(TrafficMonitor.formatTraffic(txRate) + "/s")
rxRateText.setText(TrafficMonitor.formatTraffic(rxRate) + "/s") rxRateText.setText(TrafficMonitor.formatTraffic(rxRate) + "/s")
val child = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment] val child = getSupportFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (state != ServiceState.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal) if (state != ServiceState.STOPPING && child != null)
child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
} }
override def onServiceConnected(): Unit = changeState(bgService.getState) override def onServiceConnected(): Unit = changeState(bgService.getState)
...@@ -352,7 +353,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -352,7 +353,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
} }
private def displayFragment(fragment: ToolbarFragment) { private def displayFragment(fragment: ToolbarFragment) {
getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss() getSupportFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
drawer.closeDrawer() drawer.closeDrawer()
} }
...@@ -387,7 +388,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -387,7 +388,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
} }
override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else { override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else {
val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment] val currentFragment = getSupportFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (!currentFragment.onBackPressed()) if (!currentFragment.onBackPressed())
if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed()
else drawer.setSelection(DRAWER_PROFILES) else drawer.setSelection(DRAWER_PROFILES)
......
...@@ -23,20 +23,19 @@ package com.github.shadowsocks ...@@ -23,20 +23,19 @@ package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.content.{DialogInterface, Intent} import android.content.{DialogInterface, Intent}
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.utils.Key
object ProfileConfigActivity { object ProfileConfigActivity {
final val REQUEST_CODE_PLUGIN_HELP = 1 final val REQUEST_CODE_PLUGIN_HELP = 1
} }
class ProfileConfigActivity extends Activity { class ProfileConfigActivity extends AppCompatActivity {
import ProfileConfigActivity._ import ProfileConfigActivity._
private lazy val child = getFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment] private lazy val child = getSupportFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment]
override def onCreate(savedInstanceState: Bundle) { override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
......
...@@ -30,18 +30,19 @@ import android.support.v7.preference.{Preference, PreferenceDataStore} ...@@ -30,18 +30,19 @@ import android.support.v7.preference.{Preference, PreferenceDataStore}
import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.text.TextUtils import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
import be.mygod.preference.{EditTextPreference, PreferenceFragment}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin._ import com.github.shadowsocks.plugin._
import com.github.shadowsocks.preference.{IconListPreference, OnPreferenceDataStoreChangeListener, PluginConfigurationDialogFragment} import com.github.shadowsocks.preference.{IconListPreference, OnPreferenceDataStoreChangeListener, PluginConfigurationDialogFragment}
import com.github.shadowsocks.utils.{Action, Key, Utils} import com.github.shadowsocks.utils.{Action, Key}
import com.takisoft.fix.support.v7.preference.{EditTextPreference, PreferenceFragmentCompatDividers}
object ProfileConfigFragment { object ProfileConfigFragment {
private final val REQUEST_CODE_PLUGIN_CONFIGURE = 1 private final val REQUEST_CODE_PLUGIN_CONFIGURE = 1
private final val FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG"
} }
class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener class ProfileConfigFragment extends PreferenceFragmentCompatDividers with OnMenuItemClickListener
with OnPreferenceDataStoreChangeListener { with OnPreferenceDataStoreChangeListener {
import ProfileConfigFragment._ import ProfileConfigFragment._
...@@ -51,7 +52,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -51,7 +52,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private var pluginConfigure: EditTextPreference = _ private var pluginConfigure: EditTextPreference = _
private var pluginConfiguration: PluginConfiguration = _ private var pluginConfiguration: PluginConfiguration = _
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferencesFix(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore) getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match { app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match {
case Some(p) => case Some(p) =>
...@@ -136,8 +137,12 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -136,8 +137,12 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private def showPluginEditor() { private def showPluginEditor() {
val bundle = new Bundle() val bundle = new Bundle()
bundle.putString("key", Key.pluginConfigure)
bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected) bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected)
displayPreferenceDialog(Key.pluginConfigure, new PluginConfigurationDialogFragment, bundle) val fragment = new PluginConfigurationDialogFragment
fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0)
fragment.show(getFragmentManager, FRAGMENT_DIALOG_TAG)
} }
override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference.getKey == Key.pluginConfigure) { override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference.getKey == Key.pluginConfigure) {
......
...@@ -23,8 +23,10 @@ package com.github.shadowsocks ...@@ -23,8 +23,10 @@ package com.github.shadowsocks
import java.nio.charset.Charset import java.nio.charset.Charset
import android.app.Activity import android.app.Activity
import android.content.Context
import android.nfc.{NdefMessage, NdefRecord, NfcAdapter} import android.nfc.{NdefMessage, NdefRecord, NfcAdapter}
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.{LayoutInflater, View, ViewGroup} import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{ImageView, LinearLayout} import android.widget.{ImageView, LinearLayout}
import net.glxn.qrgen.android.QRCode import net.glxn.qrgen.android.QRCode
...@@ -57,11 +59,12 @@ final class QRCodeDialog extends DialogFragment { ...@@ -57,11 +59,12 @@ final class QRCodeDialog extends DialogFragment {
image image
} }
override def onAttach(activity: Activity) { override def onAttach(context: Context) {
superOnAttach(activity) super.onAttach(context)
adapter = NfcAdapter.getDefaultAdapter(getActivity) adapter = NfcAdapter.getDefaultAdapter(getActivity)
if (adapter != null) adapter.setNdefPushMessage(new NdefMessage(Array( if (adapter != null) adapter.setNdefPushMessage(new NdefMessage(Array(
new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, nfcShareItem, Array[Byte](), nfcShareItem))), activity) new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, nfcShareItem, Array[Byte](), nfcShareItem))),
context.asInstanceOf[Activity])
} }
override def onDetach() { override def onDetach() {
......
...@@ -26,13 +26,14 @@ import android.content.pm.ShortcutManager ...@@ -26,13 +26,14 @@ import android.content.pm.ShortcutManager
import android.os.{Build, Bundle} import android.os.{Build, Bundle}
import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat} import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat}
import android.support.v4.graphics.drawable.IconCompat import android.support.v4.graphics.drawable.IconCompat
import android.support.v7.app.AppCompatActivity
import com.github.shadowsocks.bg.ServiceState import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.utils.Utils import com.github.shadowsocks.utils.Utils
/** /**
* @author Mygod * @author Mygod
*/ */
class QuickToggleShortcut extends Activity with ServiceBoundContext { class QuickToggleShortcut extends AppCompatActivity with ServiceBoundContext {
override def onCreate(savedInstanceState: Bundle) { override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
getIntent.getAction match { getIntent.getAction match {
......
...@@ -20,8 +20,7 @@ ...@@ -20,8 +20,7 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.{Activity, TaskStackBuilder} import android.app.TaskStackBuilder
import android.content.Intent
import android.content.pm.{PackageManager, ShortcutManager} import android.content.pm.{PackageManager, ShortcutManager}
import android.os.{Build, Bundle} import android.os.{Build, Bundle}
import android.support.v4.app.ActivityCompat import android.support.v4.app.ActivityCompat
...@@ -30,9 +29,9 @@ import android.support.v7.app.AppCompatActivity ...@@ -30,9 +29,9 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.text.TextUtils import android.text.TextUtils
import android.widget.Toast import android.widget.Toast
import com.google.zxing.Result
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Parser import com.github.shadowsocks.utils.Parser
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
object ScannerActivity { object ScannerActivity {
......
...@@ -33,15 +33,16 @@ import android.support.v7.app.AppCompatDelegate ...@@ -33,15 +33,16 @@ import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.bg.{BaseService, ProxyService, TransproxyService, VpnService} import com.github.shadowsocks.bg.{ProxyService, TransproxyService, VpnService}
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager} import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.preference.{BottomSheetPreferenceDialogFragment, IconListPreference, OrmLitePreferenceDataStore}
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker} import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker}
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog import com.j256.ormlite.logger.LocalLog
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
...@@ -156,6 +157,8 @@ class ShadowsocksApplication extends Application { ...@@ -156,6 +157,8 @@ class ShadowsocksApplication extends Application {
if (task.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")) if (task.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config"))
JobManager.create(this).addJobCreator(DonaldTrump) JobManager.create(this).addJobCreator(DonaldTrump)
PreferenceFragmentCompat.registerPreferenceFragment(classOf[IconListPreference],
classOf[BottomSheetPreferenceDialogFragment])
TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Fragment
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.view.View import android.view.View
......
...@@ -26,7 +26,7 @@ import android.net.Uri ...@@ -26,7 +26,7 @@ import android.net.Uri
import android.util.Base64 import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.{Key, Utils}
import com.j256.ormlite.field.{DataType, DatabaseField} import com.j256.ormlite.field.{DataType, DatabaseField}
class Profile { class Profile {
...@@ -106,7 +106,7 @@ class Profile { ...@@ -106,7 +106,7 @@ class Profile {
def serialize(store: OrmLitePreferenceDataStore) { def serialize(store: OrmLitePreferenceDataStore) {
store.putString(Key.name, name) store.putString(Key.name, name)
store.putString(Key.host, host) store.putString(Key.host, host)
store.putInt(Key.remotePort, remotePort) store.putString(Key.remotePort, remotePort.toString)
store.putString(Key.password, password) store.putString(Key.password, password)
store.putString(Key.route, route) store.putString(Key.route, route)
store.putString(Key.remoteDns, remoteDns) store.putString(Key.remoteDns, remoteDns)
...@@ -123,7 +123,7 @@ class Profile { ...@@ -123,7 +123,7 @@ class Profile {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = store.getString(Key.name, null) name = store.getString(Key.name, null)
host = store.getString(Key.host, null) host = store.getString(Key.host, null)
remotePort = store.getInt(Key.remotePort, 0) remotePort = Utils.parsePort(store.getString(Key.remotePort), 8388, 1)
password = store.getString(Key.password, null) password = store.getString(Key.password, null)
method = store.getString(Key.method, null) method = store.getString(Key.method, null)
route = store.getString(Key.route, null) route = store.getString(Key.route, null)
......
...@@ -7,7 +7,7 @@ import android.net.Uri ...@@ -7,7 +7,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.support.design.widget.BottomSheetDialog import android.support.design.widget.BottomSheetDialog
import android.support.v14.preference.PreferenceDialogFragment import android.support.v7.preference.PreferenceDialogFragmentCompat
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.{LinearLayoutManager, RecyclerView} import android.support.v7.widget.{LinearLayoutManager, RecyclerView}
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
...@@ -18,7 +18,7 @@ import com.github.shadowsocks.R ...@@ -18,7 +18,7 @@ import com.github.shadowsocks.R
/** /**
* @author Mygod * @author Mygod
*/ */
final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment { final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
private lazy val preference = getPreference.asInstanceOf[IconListPreference] private lazy val preference = getPreference.asInstanceOf[IconListPreference]
private lazy val index: Int = preference.selectedEntry private lazy val index: Int = preference.selectedEntry
private lazy val entries: Array[CharSequence] = preference.getEntries private lazy val entries: Array[CharSequence] = preference.getEntries
......
...@@ -6,15 +6,11 @@ import android.support.annotation.ArrayRes ...@@ -6,15 +6,11 @@ import android.support.annotation.ArrayRes
import android.support.v7.preference.ListPreference import android.support.v7.preference.ListPreference
import android.support.v7.preference.Preference.OnPreferenceChangeListener import android.support.v7.preference.Preference.OnPreferenceChangeListener
import android.util.AttributeSet import android.util.AttributeSet
import be.mygod.preference.DialogPreferencePlus
/** /**
* @author Mygod * @author Mygod
*/ */
class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs) class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs) {
with DialogPreferencePlus {
override def createDialog() = new BottomSheetPreferenceDialogFragment()
private var mEntryIcons: Array[Drawable] = _ private var mEntryIcons: Array[Drawable] = _
def selectedEntry: Int = getEntryValues.indexOf(getValue) def selectedEntry: Int = getEntryValues.indexOf(getValue)
......
...@@ -6,7 +6,7 @@ import java.util ...@@ -6,7 +6,7 @@ import java.util
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.{DBHelper, KeyValuePair} import com.github.shadowsocks.database.{DBHelper, KeyValuePair}
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.{Key, Utils}
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
...@@ -98,12 +98,19 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat ...@@ -98,12 +98,19 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat
def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener
def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener
private def getLocalPort(key: String) = getInt(key, 0) match {
case 0 => Utils.parsePort(getString(key), 0)
case value =>
putString(key, value.toString)
value
}
def profileId: Int = getInt(Key.id, 0) def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i) def profileId_=(i: Int): Unit = putInt(Key.id, i)
def serviceMode: String = getString(Key.serviceMode, Key.modeVpn) def serviceMode: String = getString(Key.serviceMode, Key.modeVpn)
def portProxy: Int = getInt(Key.portProxy, 0) def portProxy: Int = getLocalPort(Key.portProxy)
def portLocalDns: Int = getInt(Key.portLocalDns, 0) def portLocalDns: Int = getLocalPort(Key.portLocalDns)
def portTransproxy: Int = getInt(Key.portTransproxy, 0) def portTransproxy: Int = getLocalPort(Key.portTransproxy)
def proxyApps: Boolean = getBoolean(Key.proxyApps) def proxyApps: Boolean = getBoolean(Key.proxyApps)
def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value) def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
......
...@@ -20,9 +20,11 @@ ...@@ -20,9 +20,11 @@
package com.github.shadowsocks.preference package com.github.shadowsocks.preference
import android.app.AlertDialog
import android.content.DialogInterface import android.content.DialogInterface
import be.mygod.preference.EditTextPreferenceDialogFragment import android.support.v7.app.AlertDialog
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat
import android.view.View
import android.widget.EditText
import com.github.shadowsocks.ProfileConfigActivity import com.github.shadowsocks.ProfileConfigActivity
import com.github.shadowsocks.plugin.{PluginContract, PluginManager} import com.github.shadowsocks.plugin.{PluginContract, PluginManager}
...@@ -33,9 +35,11 @@ object PluginConfigurationDialogFragment { ...@@ -33,9 +35,11 @@ object PluginConfigurationDialogFragment {
final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID" final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
} }
class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment { class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragmentCompat {
import PluginConfigurationDialogFragment._ import PluginConfigurationDialogFragment._
private var editText: EditText = _
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) { override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder) super.onPrepareDialogBuilder(builder)
val intent = PluginManager.buildIntent(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG), PluginContract.ACTION_HELP) val intent = PluginManager.buildIntent(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG), PluginContract.ACTION_HELP)
...@@ -43,4 +47,9 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment ...@@ -43,4 +47,9 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment
getActivity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString), getActivity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString),
ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)): DialogInterface.OnClickListener) ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)): DialogInterface.OnClickListener)
} }
override def onBindDialogView(view: View) {
super.onBindDialogView(view)
editText = view.findViewById(android.R.id.edit)
}
} }
...@@ -152,4 +152,12 @@ object Utils { ...@@ -152,4 +152,12 @@ object Utils {
} }
def ThrowableFuture[T](f: => T): Unit = Future(f) onComplete handleFailure def ThrowableFuture[T](f: => T): Unit = Future(f) onComplete handleFailure
def parsePort(str: String, default: Int, min: Int = 1025): Int = (try str.toInt catch {
case _: NumberFormatException => default
}) match {
case x if x < min => default
case x if x > 65535 => default
case x => x
}
} }
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
android:orientation="vertical">
<TextView android:id="@android:id/message"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_marginBottom="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary" />
<EditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shadowsocks" parent="Theme.ShadowsocksBase"/>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberPickerPreference">
<attr name="max" format="integer" />
<attr name="min" format="integer" />
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Shadowsocks.Material" parent="Theme.AppCompat.Light.NoActionBar"/> <style name="Theme.Shadowsocks" parent="PreferenceFixTheme.Light.NoActionBar">
<style name="Theme.ShadowsocksBase" parent="Theme.Shadowsocks.Material">
<!--suppress NewApi --> <!--suppress NewApi -->
<item name="android:navigationBarColor">@color/material_primary_700</item> <item name="android:navigationBarColor">@color/material_primary_700</item>
<item name="colorAccent">@color/material_accent_200</item> <item name="colorAccent">@color/material_accent_200</item>
<item name="colorButtonNormal">@color/material_accent_200</item> <item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item> <item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item> <item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Shadowsocks.PreferenceThemeOverlay</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.Fix</item>
</style> <item name="preferenceCategory_marginBottom">8dp</item>
<style name="Theme.Shadowsocks.Dialog" parent="Theme.AppCompat.Light.Dialog">
<item name="colorAccent">@color/material_primary_500</item>
<item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Shadowsocks.PreferenceThemeOverlay</item>
<item name="windowNoTitle">true</item>
</style>
<!-- Fix for wrong padding on v17-v20 based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/a6082cb/preference-v7/src/main/res/values-v17/styles.xml -->
<style name="Theme.Shadowsocks" parent="Theme.ShadowsocksBase">
<item name="android:listPreferredItemPaddingStart">16dp</item>
<item name="android:listPreferredItemPaddingEnd">16dp</item>
</style>
<!-- This is needed because the default PreferenceCategory appearance doesn't use bold text style -->
<style name="Preference_TextAppearanceMaterialBody2">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Theme.Shadowsocks.PreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay.v14.Material">
<item name="preferenceFragmentListStyle">@style/Theme.Shadowsocks.PreferenceFragmentList</item>
</style>
<style name="Theme.Shadowsocks.PreferenceFragmentList" parent="@style/PreferenceFragmentList.Material">
<item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item>
</style>
<style name="NumberPickerStyle">
<item name="android:textSize">18sp</item>
</style> </style>
</resources> </resources>
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.app.DialogFragment
import android.support.v7.preference.DialogPreference
trait DialogPreferencePlus extends DialogPreference {
def createDialog(): DialogFragment
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.content.Context
import android.support.v7.preference.{EditTextPreference => Parent}
import android.support.v7.widget.AppCompatEditText
import android.text.InputType
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.FrameLayout
import com.github.shadowsocks.plugin.R
/**
* Fixed EditTextPreference + SummaryPreference with password support!
* Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference
*/
class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs)
with DialogPreferencePlus with SummaryPreference {
val editText = new AppCompatEditText(context, attrs)
editText.setId(android.R.id.edit)
{
val arr = context.obtainStyledAttributes(Array(R.attr.dialogPreferredPadding))
val margin = arr.getDimensionPixelOffset(0, 0)
arr.recycle()
val params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(margin, 0, margin, 0)
editText.setLayoutParams(params)
}
override def createDialog() = new EditTextPreferenceDialogFragment()
override protected def getSummaryValue: String = {
var text = getText
if (text == null) text = ""
val inputType = editText.getInputType
if (inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) ||
inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) ||
inputType == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD))
"\u2022" * text.length else text
}
override def setText(text: String): Unit = {
val old = getText
super.setText(text)
if (old != text) notifyChanged()
}
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.support.v14.preference.PreferenceDialogFragment
import android.support.v7.widget.AppCompatEditText
import android.view.{View, ViewGroup}
class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
protected lazy val editText: AppCompatEditText = preference.editText
override protected def onBindDialogView(view: View) {
super.onBindDialogView(view)
editText.setText(preference.getText)
val text = editText.getText
if (text != null) editText.setSelection(0, text.length)
val oldParent = editText.getParent.asInstanceOf[ViewGroup]
if (oldParent eq view) return
if (oldParent != null) oldParent.removeView(editText)
val oldEdit = view.findViewById[View](android.R.id.edit)
if (oldEdit == null) return
val container = oldEdit.getParent.asInstanceOf[ViewGroup]
if (container == null) return
container.removeView(oldEdit)
container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
override protected def needInputMethod = true
def onDialogClosed(positiveResult: Boolean): Unit = if (positiveResult) {
val value = editText.getText.toString
if (preference.callChangeListener(value)) preference.setText(value)
}
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.content.Context
import android.content.res.TypedArray
import android.support.v7.preference.DialogPreference
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.widget.NumberPicker
import com.github.shadowsocks.plugin.R
class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference {
private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle))
private var value: Int = _
{
val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference)
setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0))
setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1))
a.recycle()
}
override def createDialog() = new NumberPickerPreferenceDialogFragment()
def getValue: Int = value
def getMin: Int = if (picker == null) 0 else picker.getMinValue
def getMax: Int = picker.getMaxValue
def setValue(i: Int) {
if (i == value) return
picker.setValue(i)
value = picker.getValue
persistInt(value)
notifyChanged()
}
def setMin(value: Int): Unit = picker.setMinValue(value)
def setMax(value: Int): Unit = picker.setMaxValue(value)
override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef =
a.getInt(index, getMin).asInstanceOf[AnyRef]
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
val default = defaultValue.asInstanceOf[Int]
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
}
protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef]
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment
import android.view.{View, ViewGroup}
import android.widget.NumberPicker
class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
private lazy val picker = preference.picker
override protected def onCreateDialogView(context: Context): NumberPicker = {
val parent = picker.getParent.asInstanceOf[ViewGroup]
if (parent != null) parent.removeView(picker)
picker
}
override protected def onBindDialogView(view: View) {
super.onBindDialogView(view)
picker.setValue(preference.getValue)
}
override protected def needInputMethod = true
def onDialogClosed(positiveResult: Boolean) {
picker.clearFocus() // commit changes
if (positiveResult) {
val value = picker.getValue
if (preference.callChangeListener(value)) preference.setValue(value)
}
}
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.content.Context
import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
import android.util.AttributeSet
import android.view.ViewGroup.MarginLayoutParams
/**
* Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/4c2bb2896895dbedb37b659b36bd2a96b33c1605/preference-v7/src/main/java/com/takisoft/fix/support/v7/preference/PreferenceCategory.java
*
* @author Mygod
*/
class PreferenceCategory(context: Context, attrs: AttributeSet = null) extends Base(context, attrs) {
override def onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
holder.findViewById(android.R.id.title).getLayoutParams.asInstanceOf[MarginLayoutParams].bottomMargin = 0
}
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.app.DialogFragment
import android.os.Bundle
import android.support.v14.preference.{PreferenceFragment => Base}
import android.support.v7.preference.{Preference, PreferenceScreen}
import android.view.{LayoutInflater, View, ViewGroup}
abstract class PreferenceFragment extends Base {
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
super.onCreateView(inflater, container, savedInstanceState)
protected final def displayPreferenceDialog(key: String, fragment: DialogFragment, other: Bundle = null) {
val bundle = new Bundle(1)
bundle.putString("key", key)
if (other != null) bundle.putAll(other)
fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0)
getFragmentManager.beginTransaction()
.add(fragment, "android.support.v14.preference.PreferenceFragment.DIALOG")
.commitAllowingStateLoss()
}
override def onDisplayPreferenceDialog(preference: Preference): Unit = preference match {
case dpp: DialogPreferencePlus => displayPreferenceDialog(preference.getKey, dpp.createDialog())
case _ => super.onDisplayPreferenceDialog(preference)
}
override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
override def onResume() {
super.onResume()
getListView.scrollBy(0, 0)
}
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import java.lang.reflect.Field
import java.util
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old}
import android.view.{LayoutInflater, View, ViewGroup}
import com.github.shadowsocks.plugin.R
/**
* Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69
*
* @author Mygod
*/
object PreferenceGroupAdapter {
private var preferenceLayoutsField: Field = _
private var fieldResId: Field = _
private var fieldWidgetResId: Field = _
private val preferenceViewHolderConstructor = classOf[PreferenceViewHolder].getDeclaredConstructor(classOf[View])
{
val oldClass = classOf[Old]
preferenceLayoutsField = oldClass.getDeclaredField("mPreferenceLayouts")
preferenceLayoutsField.setAccessible(true)
val c = oldClass.getDeclaredClasses.filter(c => c.getSimpleName == "PreferenceLayout").head
fieldResId = c.getDeclaredField("resId")
fieldResId.setAccessible(true)
fieldWidgetResId = c.getDeclaredField("widgetResId")
fieldWidgetResId.setAccessible(true)
preferenceViewHolderConstructor.setAccessible(true)
}
}
class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) {
import PreferenceGroupAdapter._
protected lazy val preferenceLayouts: util.List[AnyRef] =
preferenceLayoutsField.get(this).asInstanceOf[util.List[AnyRef]]
override def onCreateViewHolder(parent: ViewGroup, viewType: Int): PreferenceViewHolder =
if (Build.VERSION.SDK_INT < 21) {
val context = parent.getContext
val inflater = LayoutInflater.from(context)
val pl = preferenceLayouts.get(viewType)
val view = inflater.inflate(fieldResId.get(pl).asInstanceOf[Int], parent, false)
if (view.getBackground == null) {
val array = context.obtainStyledAttributes(null, R.styleable.BackgroundStyle)
var background = array.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground)
if (background == null)
background = ContextCompat.getDrawable(context, android.R.drawable.list_selector_background)
array.recycle()
val (s, t, e, b) = (ViewCompat.getPaddingStart(view), view.getPaddingTop,
ViewCompat.getPaddingEnd(view), view.getPaddingBottom)
view.setBackground(background)
ViewCompat.setPaddingRelative(view, s, t, e, b)
}
val widgetFrame = view.findViewById[ViewGroup](android.R.id.widget_frame)
if (widgetFrame != null) {
val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int]
if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE)
}
preferenceViewHolderConstructor.newInstance(view)
} else super.onCreateViewHolder(parent, viewType)
}
/*******************************************************************************/
/* */
/* 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 be.mygod.preference
import android.support.v7.preference.Preference
/**
* Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
* Based on:
* https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/ListPreference.java
*
* @author Mygod
*/
trait SummaryPreference extends Preference {
protected def getSummaryValue: AnyRef
/**
* Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
* (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
*
* @return the summary with appropriate string substitution
*/
override def getSummary: String = {
val summary = super.getSummary
if (summary == null) null else String.format(summary.toString, getSummaryValue)
}
}
package com.github.shadowsocks.plugin package com.github.shadowsocks.plugin
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast import android.widget.Toast
/** /**
...@@ -10,7 +10,7 @@ import android.widget.Toast ...@@ -10,7 +10,7 @@ import android.widget.Toast
* *
* @author Mygod * @author Mygod
*/ */
trait OptionsCapableActivity extends Activity { trait OptionsCapableActivity extends AppCompatActivity {
protected def pluginOptions(intent: Intent = getIntent): PluginOptions = protected def pluginOptions(intent: Intent = getIntent): PluginOptions =
try new PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS)) catch { try new PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS)) catch {
case exc: IllegalArgumentException => case exc: IllegalArgumentException =>
......
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