Commit 0282975e authored by Max Lv's avatar Max Lv

Merge pull request #490 from Mygod/master

Enhancements for profile selector, and bugfixes
parents 2a5d8846 c4583bea
...@@ -29,13 +29,13 @@ resolvers += "JRAF" at "http://JRAF.org/static/maven/2" ...@@ -29,13 +29,13 @@ resolvers += "JRAF" at "http://JRAF.org/static/maven/2"
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"dnsjava" % "dnsjava" % "2.1.7", "dnsjava" % "dnsjava" % "2.1.7",
"com.github.kevinsawicki" % "http-request" % "6.0", "com.github.kevinsawicki" % "http-request" % "6.0",
"commons-net" % "commons-net" % "3.3", "commons-net" % "commons-net" % "3.4",
"eu.chainfire" % "libsuperuser" % "1.0.0.201510071325", "eu.chainfire" % "libsuperuser" % "1.0.0.201510071325",
"com.google.zxing" % "android-integration" % "3.2.1", "com.google.zxing" % "android-integration" % "3.2.1",
"net.glxn.qrgen" % "android" % "2.0", "net.glxn.qrgen" % "android" % "2.0",
"com.google.android.gms" % "play-services-base" % "8.3.0", "com.google.android.gms" % "play-services-base" % "8.4.0",
"com.google.android.gms" % "play-services-ads" % "8.3.0", "com.google.android.gms" % "play-services-ads" % "8.4.0",
"com.google.android.gms" % "play-services-analytics" % "8.3.0", "com.google.android.gms" % "play-services-analytics" % "8.4.0",
"com.android.support" % "design" % "23.1.1", "com.android.support" % "design" % "23.1.1",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.j256.ormlite" % "ormlite-core" % "4.48", "com.j256.ormlite" % "ormlite-core" % "4.48",
......
...@@ -85,6 +85,10 @@ ...@@ -85,6 +85,10 @@
<string name="connection_test_error_status_code">状态码无效。(#%d)</string> <string name="connection_test_error_status_code">状态码无效。(#%d)</string>
<!-- profile --> <!-- profile -->
<string name="profile_manager_dialog">配置文件</string>
<string name="profile_manager_dialog_content">&#8226; 点击选择配置文件;\n&#8226; 滑动删除;\n&#8226;
长按拖动改变顺序。</string>
<string name="gotcha">好的</string>
<string name="add_profile_dialog">为影梭添加此配置文件?</string> <string name="add_profile_dialog">为影梭添加此配置文件?</string>
<string name="add_profile_methods_scan_qr_code">扫描二维码</string> <string name="add_profile_methods_scan_qr_code">扫描二维码</string>
<string name="add_profile_methods_manual_settings">手动设置</string> <string name="add_profile_methods_manual_settings">手动设置</string>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<resources> <resources>
<color name="material_green_a700">#64DD17</color> <color name="material_green_700">#388E3C</color>
<color name="material_blue_grey_300">#90A4AE</color> <color name="material_blue_grey_300">#90A4AE</color>
<color name="material_blue_grey_500">#607D8B</color> <color name="material_blue_grey_500">#607D8B</color>
<color name="material_blue_grey_700">#455A64</color> <color name="material_blue_grey_700">#455A64</color>
......
...@@ -103,6 +103,10 @@ ...@@ -103,6 +103,10 @@
<string name="action_import_err">Failed to import.</string> <string name="action_import_err">Failed to import.</string>
<!-- profile --> <!-- profile -->
<string name="profile_manager_dialog">Profiles</string>
<string name="profile_manager_dialog_content">&#8226; Tap to select a profile;\n&#8226; Swipe to remove;\n&#8226;
Long press to reorder profiles.</string>
<string name="gotcha">Gotcha!</string>
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string> <string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
<string name="add_profile_methods_scan_qr_code">Scan QR code</string> <string name="add_profile_methods_scan_qr_code">Scan QR code</string>
<string name="add_profile_methods_manual_settings">Manual Settings</string> <string name="add_profile_methods_manual_settings">Manual Settings</string>
......
...@@ -134,6 +134,7 @@ trait BaseService extends Service { ...@@ -134,6 +134,7 @@ trait BaseService extends Service {
def updateTrafficTotal(tx: Long, rx: Long) { def updateTrafficTotal(tx: Long, rx: Long) {
val handler = new Handler(getContext.getMainLooper) val handler = new Handler(getContext.getMainLooper)
handler.post(() => { handler.post(() => {
val config = this.config // avoid race conditions without locking
if (config != null) { if (config != null) {
ShadowsocksApplication.profileManager.getProfile(config.profileId) match { ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
case Some(profile) => case Some(profile) =>
......
package com.github.shadowsocks package com.github.shadowsocks
import android.content.Intent import android.content.Intent
import android.os.{Handler, Bundle} import android.os.{Bundle, Handler}
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
...@@ -25,7 +25,13 @@ import scala.collection.mutable.ArrayBuffer ...@@ -25,7 +25,13 @@ import scala.collection.mutable.ArrayBuffer
/** /**
* @author Mygod * @author Mygod
*/ */
object ProfileManagerActivity {
private final val profileTip = "profileTip"
}
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext { class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext {
import ProfileManagerActivity._
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private var item: Profile = _ private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
...@@ -72,11 +78,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -72,11 +78,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
handler.post(() => text.setText(builder)) handler.post(() => text.setText(builder))
} }
def bind(item: Profile) { def bind(item: Profile, i: Int) {
this.item = item this.item = item
updateText() updateText()
if (item.id == ShadowsocksApplication.profileId) { if (item.id == ShadowsocksApplication.profileId) {
text.setChecked(true) text.setChecked(true)
selectedIndex = i
selectedItem = this selectedItem = this
} else { } else {
text.setChecked(false) text.setChecked(false)
...@@ -92,12 +99,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -92,12 +99,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
private val recycleBin = new ArrayBuffer[(Int, Profile)] private val recycleBin = new ArrayBuffer[(Int, Profile)]
private var profiles = new ArrayBuffer[Profile] var profiles = new ArrayBuffer[Profile]
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile]) profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
def getItemCount = profiles.length def getItemCount = profiles.length
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i)) def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i), i)
def onCreateViewHolder(vg: ViewGroup, i: Int) = def onCreateViewHolder(vg: ViewGroup, i: Int) =
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false)) new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
...@@ -110,6 +117,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -110,6 +117,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
notifyItemInserted(pos) notifyItemInserted(pos)
} }
def move(from: Int, to: Int) {
val step = if (from < to) 1 else -1
val first = profiles(from)
var previousOrder = profiles(from).userOrder
for (i <- from until to by step) {
val next = profiles(i + step)
val order = next.userOrder
next.userOrder = previousOrder
previousOrder = order
profiles(i) = next
ShadowsocksApplication.profileManager.updateProfile(next)
}
first.userOrder = previousOrder
profiles(to) = first
ShadowsocksApplication.profileManager.updateProfile(first)
notifyItemMoved(from, to)
}
def remove(pos: Int) { def remove(pos: Int) {
recycleBin.append((pos, profiles(pos))) recycleBin.append((pos, profiles(pos)))
profiles.remove(pos) profiles.remove(pos)
...@@ -131,6 +156,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -131,6 +156,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
} }
} }
private var selectedIndex = -1
private var selectedItem: ProfileViewHolder = _ private var selectedItem: ProfileViewHolder = _
private val handler = new Handler private val handler = new Handler
...@@ -153,21 +179,30 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -153,21 +179,30 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add) ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView] val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
profilesList.setLayoutManager(new LinearLayoutManager(this)) val layoutManager = new LinearLayoutManager(this)
profilesList.setLayoutManager(layoutManager)
profilesList.setItemAnimator(new DefaultItemAnimator) profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter) profilesList.setAdapter(profilesAdapter)
if (selectedIndex < 0) selectedIndex = profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i
}.getOrElse(-1)
layoutManager.scrollToPosition(selectedIndex)
removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG) removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener) .setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener { removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
def onViewAttachedToWindow(v: View) = () def onViewAttachedToWindow(v: View) = ()
}) })
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) { new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.START | ItemTouchHelper.END) {
def onSwiped(viewHolder: ViewHolder, direction: Int) = { def onSwiped(viewHolder: ViewHolder, direction: Int) = {
profilesAdapter.remove(viewHolder.getAdapterPosition) profilesAdapter.remove(viewHolder.getAdapterPosition)
removedSnackbar.show removedSnackbar.show
} }
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO? def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = {
profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition)
true
}
}).attachToRecyclerView(profilesList) }).attachToRecyclerView(profilesList)
attachService(new IShadowsocksServiceCallback.Stub { attachService(new IShadowsocksServiceCallback.Stub {
...@@ -175,6 +210,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -175,6 +210,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) = def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) =
if (selectedItem != null) selectedItem.updateText(true) if (selectedItem != null) selectedItem.updateText(true)
}) })
if (ShadowsocksApplication.settings.getBoolean(profileTip, true)) {
ShadowsocksApplication.settings.edit.putBoolean(profileTip, false).commit
new AlertDialog.Builder(this).setTitle(R.string.profile_manager_dialog)
.setMessage(R.string.profile_manager_dialog_content).setPositiveButton(R.string.gotcha, null).create.show
}
} }
override def onDestroy { override def onDestroy {
......
...@@ -49,7 +49,6 @@ import android.content.res.AssetManager ...@@ -49,7 +49,6 @@ import android.content.res.AssetManager
import android.graphics.Typeface import android.graphics.Typeface
import android.net.VpnService import android.net.VpnService
import android.os._ import android.os._
import android.preference.{Preference, SwitchPreference}
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.AppCompatActivity import android.support.v7.app.AppCompatActivity
...@@ -60,7 +59,6 @@ import android.widget._ ...@@ -60,7 +59,6 @@ import android.widget._
import com.github.jorgecastilloprz.FABProgressCircle import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database._ import com.github.shadowsocks.database._
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.google.android.gms.ads.{AdRequest, AdSize, AdView} import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
...@@ -91,52 +89,9 @@ object Shadowsocks { ...@@ -91,52 +89,9 @@ object Shadowsocks {
// Constants // Constants
val TAG = "Shadowsocks" val TAG = "Shadowsocks"
val REQUEST_CONNECT = 1 val REQUEST_CONNECT = 1
val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
Key.isAuth)
val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
Executable.TUN2SOCKS) Executable.TUN2SOCKS)
// Helper functions
def updateDropDownPreference(pref: Preference, value: String) {
pref.asInstanceOf[DropDownPreference].setValue(value)
}
def updatePasswordEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
}
def updateNumberPickerPreference(pref: Preference, value: Int) {
pref.asInstanceOf[NumberPickerPreference].setValue(value)
}
def updateSummaryEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
}
def updateSwitchPreference(pref: Preference, value: Boolean) {
pref.asInstanceOf[SwitchPreference].setChecked(value)
}
def updatePreference(pref: Preference, name: String, profile: Profile) {
name match {
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password)
case Key.encMethod => updateDropDownPreference(pref, profile.method)
case Key.route => updateDropDownPreference(pref, profile.route)
case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps)
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
}
}
} }
class Shadowsocks class Shadowsocks
...@@ -166,7 +121,7 @@ class Shadowsocks ...@@ -166,7 +121,7 @@ class Shadowsocks
fab.setImageResource(R.drawable.ic_cloud_queue) fab.setImageResource(R.drawable.ic_cloud_queue)
fab.setEnabled(false) fab.setEnabled(false)
fabProgressCircle.show() fabProgressCircle.show()
setPreferenceEnabled(enabled = false) preferences.setEnabled(false)
case State.CONNECTED => case State.CONNECTED =>
fab.setBackgroundTintList(greenTint) fab.setBackgroundTintList(greenTint)
if (state == State.CONNECTING) { if (state == State.CONNECTING) {
...@@ -176,7 +131,7 @@ class Shadowsocks ...@@ -176,7 +131,7 @@ class Shadowsocks
} }
fab.setEnabled(true) fab.setEnabled(true)
changeSwitch(checked = true) changeSwitch(checked = true)
setPreferenceEnabled(enabled = false) preferences.setEnabled(false)
case State.STOPPED => case State.STOPPED =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
handler.postDelayed(() => fabProgressCircle.hide(), 1000) handler.postDelayed(() => fabProgressCircle.hide(), 1000)
...@@ -189,13 +144,13 @@ class Shadowsocks ...@@ -189,13 +144,13 @@ class Shadowsocks
(_ => preferences.natSwitch.setChecked(false)): View.OnClickListener) (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
snackbar.show snackbar.show
} }
setPreferenceEnabled(enabled = true) preferences.setEnabled(true)
case State.STOPPING => case State.STOPPING =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
fab.setImageResource(R.drawable.ic_cloud_queue) fab.setImageResource(R.drawable.ic_cloud_queue)
fab.setEnabled(false) fab.setEnabled(false)
if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped
setPreferenceEnabled(enabled = false) preferences.setEnabled(false)
} }
state = s state = s
}) })
...@@ -255,7 +210,7 @@ class Shadowsocks ...@@ -255,7 +210,7 @@ class Shadowsocks
getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings] getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings]
private var adView: AdView = _ private var adView: AdView = _
private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700) private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700)
private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_a700) private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_700)
var handler = new Handler() var handler = new Handler()
...@@ -367,12 +322,7 @@ class Shadowsocks ...@@ -367,12 +322,7 @@ class Shadowsocks
changeSwitch(checked = false) changeSwitch(checked = false)
} }
def isReady: Boolean = { def isReady: Boolean = checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null
if (!checkText(Key.proxy)) return false
if (!checkText(Key.sitekey)) return false
if (bgService == null) return false
true
}
def prepareStartService() { def prepareStartService() {
ThrowableFuture { ThrowableFuture {
...@@ -438,9 +388,10 @@ class Shadowsocks ...@@ -438,9 +388,10 @@ class Shadowsocks
currentProfile = ShadowsocksApplication.currentProfile match { currentProfile = ShadowsocksApplication.currentProfile match {
case Some(profile) => profile // updated case Some(profile) => profile // updated
case None => // removed case None => // removed
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]()) ShadowsocksApplication.profileManager.getFirstProfile match {
if (profiles.isEmpty) ShadowsocksApplication.profileManager.createDefault() case Some(first) => ShadowsocksApplication.switchProfile(first.id)
else ShadowsocksApplication.switchProfile(profiles.head.id) case None => ShadowsocksApplication.profileManager.createDefault()
}
} }
updatePreferenceScreen() updatePreferenceScreen()
...@@ -461,25 +412,25 @@ class Shadowsocks ...@@ -461,25 +412,25 @@ class Shadowsocks
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
serviceStarted = false serviceStarted = false
fab.setImageResource(R.drawable.ic_cloud_queue) fab.setImageResource(R.drawable.ic_cloud_queue)
setPreferenceEnabled(false) preferences.setEnabled(false)
fabProgressCircle.show() fabProgressCircle.show()
case State.CONNECTED => case State.CONNECTED =>
fab.setBackgroundTintList(greenTint) fab.setBackgroundTintList(greenTint)
serviceStarted = true serviceStarted = true
fab.setImageResource(R.drawable.ic_cloud) fab.setImageResource(R.drawable.ic_cloud)
setPreferenceEnabled(false) preferences.setEnabled(false)
fabProgressCircle.postDelayed(fabProgressCircle.hide, 100) fabProgressCircle.postDelayed(fabProgressCircle.hide, 100)
case State.STOPPING => case State.STOPPING =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
serviceStarted = false serviceStarted = false
fab.setImageResource(R.drawable.ic_cloud_queue) fab.setImageResource(R.drawable.ic_cloud_queue)
setPreferenceEnabled(false) preferences.setEnabled(false)
fabProgressCircle.show() fabProgressCircle.show()
case _ => case _ =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
serviceStarted = false serviceStarted = false
fab.setImageResource(R.drawable.ic_cloud_off) fab.setImageResource(R.drawable.ic_cloud_off)
setPreferenceEnabled(true) preferences.setEnabled(true)
fabProgressCircle.postDelayed(fabProgressCircle.hide, 100) fabProgressCircle.postDelayed(fabProgressCircle.hide, 100)
} }
state = bgService.getState state = bgService.getState
...@@ -497,26 +448,6 @@ class Shadowsocks ...@@ -497,26 +448,6 @@ class Shadowsocks
updateTraffic() updateTraffic()
} }
private def setPreferenceEnabled(enabled: Boolean) {
preferences.findPreference(Key.isNAT).setEnabled(enabled)
for (name <- Shadowsocks.PROXY_PREFS) {
val pref = preferences.findPreference(name)
if (pref != null) {
pref.setEnabled(enabled)
}
}
for (name <- Shadowsocks.FEATURE_PREFS) {
val pref = preferences.findPreference(name)
if (pref != null) {
if (name == Key.isProxyApps) {
pref.setEnabled(enabled && (Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
} else {
pref.setEnabled(enabled)
}
}
}
}
private def updatePreferenceScreen() { private def updatePreferenceScreen() {
val profile = currentProfile val profile = currentProfile
if (profile.host == "198.199.101.152" && adView == null) { if (profile.host == "198.199.101.152" && adView == null) {
...@@ -527,14 +458,7 @@ class Shadowsocks ...@@ -527,14 +458,7 @@ class Shadowsocks
adView.loadAd(new AdRequest.Builder().build()) adView.loadAd(new AdRequest.Builder().build())
} }
for (name <- Shadowsocks.PROXY_PREFS) { preferences.update(profile)
val pref = preferences.findPreference(name)
Shadowsocks.updatePreference(pref, name, profile)
}
for (name <- Shadowsocks.FEATURE_PREFS) {
val pref = preferences.findPreference(name)
Shadowsocks.updatePreference(pref, name, profile)
}
} }
override def onStop() { override def onStop() {
...@@ -542,8 +466,11 @@ class Shadowsocks ...@@ -542,8 +466,11 @@ class Shadowsocks
clearDialog() clearDialog()
} }
private var _isDestroyed: Boolean = _
override def isDestroyed = if (Build.VERSION.SDK_INT >= 17) super.isDestroyed else _isDestroyed
override def onDestroy() { override def onDestroy() {
super.onDestroy() super.onDestroy()
_isDestroyed = true
deattachService() deattachService()
new BackupManager(this).dataChanged() new BackupManager(this).dataChanged()
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
...@@ -613,8 +540,8 @@ class Shadowsocks ...@@ -613,8 +540,8 @@ class Shadowsocks
} }
def clearDialog() { def clearDialog() {
if (progressDialog != null) { if (progressDialog != null && progressDialog.isShowing) {
progressDialog.dismiss() if (!isDestroyed) progressDialog.dismiss()
progressDialog = null progressDialog = null
progressTag = -1 progressTag = -1
} }
......
...@@ -11,11 +11,61 @@ import android.os.{Build, Bundle} ...@@ -11,11 +11,61 @@ import android.os.{Build, Bundle}
import android.preference.{Preference, PreferenceFragment, SwitchPreference} import android.preference.{Preference, PreferenceFragment, SwitchPreference}
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.webkit.{WebView, WebViewClient} import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.{Key, Utils}
object ShadowsocksSettings {
// Constants
val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
Key.isAuth)
val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
// Helper functions
def updateDropDownPreference(pref: Preference, value: String) {
pref.asInstanceOf[DropDownPreference].setValue(value)
}
def updatePasswordEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
}
def updateNumberPickerPreference(pref: Preference, value: Int) {
pref.asInstanceOf[NumberPickerPreference].setValue(value)
}
def updateSummaryEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
}
def updateSwitchPreference(pref: Preference, value: Boolean) {
pref.asInstanceOf[SwitchPreference].setChecked(value)
}
def updatePreference(pref: Preference, name: String, profile: Profile) {
name match {
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password)
case Key.encMethod => updateDropDownPreference(pref, profile.method)
case Key.route => updateDropDownPreference(pref, profile.route)
case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps)
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
}
}
}
// TODO: Move related logic here
class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener { class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener {
import ShadowsocksSettings._
private def activity = getActivity.asInstanceOf[Shadowsocks] private def activity = getActivity.asInstanceOf[Shadowsocks]
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference] lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
var stat: Preference = _ var stat: Preference = _
...@@ -122,10 +172,38 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan ...@@ -122,10 +172,38 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
} }
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match { def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
case Key.isNAT => activity.handler.post(() => { case Key.isNAT =>
activity.deattachService activity.handler.post(() => {
activity.attachService activity.deattachService
}) activity.attachService
})
setEnabled(enabled)
case _ => case _ =>
} }
private var enabled = true
def setEnabled(enabled: Boolean) {
this.enabled = enabled
findPreference(Key.isNAT).setEnabled(enabled)
for (name <- PROXY_PREFS) {
val pref = findPreference(name)
if (pref != null) pref.setEnabled(enabled)
}
for (name <- FEATURE_PREFS) {
val pref = findPreference(name)
if (pref != null) pref.setEnabled(enabled &&
(name != Key.isProxyApps || Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
}
}
def update(profile: Profile) {
for (name <- PROXY_PREFS) {
val pref = findPreference(name)
updatePreference(pref, name, profile)
}
for (name <- FEATURE_PREFS) {
val pref = findPreference(name)
updatePreference(pref, name, profile)
}
}
} }
...@@ -64,7 +64,7 @@ object DBHelper { ...@@ -64,7 +64,7 @@ object DBHelper {
} }
class DBHelper(val context: Context) class DBHelper(val context: Context)
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 14) { extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 15) {
import DBHelper._ import DBHelper._
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
...@@ -116,6 +116,15 @@ class DBHelper(val context: Context) ...@@ -116,6 +116,15 @@ class DBHelper(val context: Context)
profileDao.update(profile) profileDao.update(profile)
} }
} }
if (oldVersion < 15) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
var i = 0
for (profile <- profileDao.queryForAll.asScala) {
profile.userOrder = i
profileDao.update(profile)
i += 1
}
}
} }
} }
} }
...@@ -55,13 +55,13 @@ class Profile { ...@@ -55,13 +55,13 @@ class Profile {
var localPort: Int = 1080 var localPort: Int = 1080
@DatabaseField @DatabaseField
var remotePort: Int = 8338 var remotePort: Int = 8388
@DatabaseField @DatabaseField
var password: String = "" var password: String = ""
@DatabaseField @DatabaseField
var method: String = "rc4" var method: String = "aes-256-cfb"
@DatabaseField @DatabaseField
var route: String = "all" var route: String = "all"
...@@ -92,4 +92,7 @@ class Profile { ...@@ -92,4 +92,7 @@ class Profile {
@DatabaseField @DatabaseField
val date: java.util.Date = new java.util.Date() val date: java.util.Date = new java.util.Date()
@DatabaseField
var userOrder: Long = _
} }
...@@ -64,6 +64,9 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { ...@@ -64,6 +64,9 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
profile.udpdns = oldProfile.udpdns profile.udpdns = oldProfile.udpdns
case _ => case _ =>
} }
val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)")
.prepareStatementString).getFirstResult
if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1
dbHelper.profileDao.createOrUpdate(profile) dbHelper.profileDao.createOrUpdate(profile)
if (profileAddedListener != null) profileAddedListener(profile) if (profileAddedListener != null) profileAddedListener(profile)
profile profile
...@@ -109,10 +112,21 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { ...@@ -109,10 +112,21 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
} }
} }
def getFirstProfile = {
try {
val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare)
if (result.size == 1) Option(result.get(0)) else None
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
None
}
}
def getAllProfiles: Option[List[Profile]] = { def getAllProfiles: Option[List[Profile]] = {
try { try {
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
Option(dbHelper.profileDao.queryForAll().toList) Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList)
} catch { } catch {
case ex: Exception => case ex: Exception =>
Log.e(Shadowsocks.TAG, "getAllProfiles", ex) Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
......
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