Add more material design (3)

This commit is contained in:
Mygod
2015-11-09 01:58:36 +08:00
parent 1f695b9c8e
commit 51e155c940
26 changed files with 473 additions and 896 deletions
@@ -39,32 +39,26 @@
package com.github.shadowsocks
import android.app.{Activity, ProgressDialog}
import android.content.ClipboardManager
import android.content.ClipData
import android.content.Context
import android.content.SharedPreferences
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream}
import android.app.ProgressDialog
import android.content.{ClipData, ClipboardManager, Context, SharedPreferences}
import android.content.pm.PackageManager
import android.graphics.{Bitmap, PixelFormat}
import android.os.Bundle
import android.os.Handler
import android.os.{Bundle, Handler}
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.WindowManager
import android.widget._
import android.view.{Menu, MenuItem, View, ViewGroup, WindowManager}
import android.widget.AbsListView.OnScrollListener
import android.widget.CompoundButton.OnCheckedChangeListener
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import java.io.{ByteArrayOutputStream, ByteArrayInputStream, InputStream}
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
import android.widget._
import com.github.shadowsocks.utils.{Key, Scheme, Utils}
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@@ -105,7 +99,7 @@ object AppManager {
}
}
class AppManager extends Activity with OnCheckedChangeListener with OnClickListener {
class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnClickListener {
val MSG_LOAD_START = 1
val MSG_LOAD_FINISH = 2
@@ -57,14 +57,14 @@ class ParserActivity extends Activity {
new AlertDialog.Builder(this)
.setTitle(R.string.add_profile_dialog)
.setCancelable(false)
.setPositiveButton(R.string.yes, ((dialog: DialogInterface, id: Int) => {
.setPositiveButton(android.R.string.yes, ((dialog: DialogInterface, id: Int) => {
Parser.parse(data) match {
case Some(profile) => addProfile(profile)
case _ => // ignore
}
dialog.dismiss()
}): DialogInterface.OnClickListener)
.setNegativeButton(R.string.no, ((dialog: DialogInterface, id: Int) => {
.setNegativeButton(android.R.string.no, ((dialog: DialogInterface, id: Int) => {
dialog.dismiss()
finish()
}): DialogInterface.OnClickListener)
@@ -90,7 +90,7 @@ class ParserActivity extends Activity {
h.postDelayed(() => {
val profileManager =
new ProfileManager(PreferenceManager.getDefaultSharedPreferences(getBaseContext),
getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
ShadowsocksApplication.dbHelper)
profileManager.createOrUpdateProfile(profile)
profileManager.reload(profile.id)
h.sendEmptyMessage(0)
@@ -0,0 +1,176 @@
package com.github.shadowsocks
import android.app.AlertDialog
import android.content.{DialogInterface, Intent}
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.support.v7.widget.helper.ItemTouchHelper
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
import android.widget.{LinearLayout, ImageView, CheckedTextView, Toast}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Parser, Utils}
import com.google.zxing.integration.android.IntentIntegrator
import net.glxn.qrgen.android.QRCode
import scala.collection.mutable.ArrayBuffer
/**
* @author Mygod
*/
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener {
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
itemView.setOnClickListener(this)
{
val qrcode = itemView.findViewById(R.id.qrcode)
qrcode.setOnClickListener((v: View) => {
val image = new ImageView(ProfileManagerActivity.this)
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
val qrcode = QRCode.from(Parser.generate(item))
.withSize(Utils.dpToPx(ProfileManagerActivity.this, 250), Utils.dpToPx(ProfileManagerActivity.this, 250))
.asInstanceOf[QRCode]
image.setImageBitmap(qrcode.bitmap())
new AlertDialog.Builder(ProfileManagerActivity.this)
.setCancelable(true)
.setNegativeButton(getString(R.string.close),
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
.setView(image)
.create()
.show()
})
qrcode.setOnLongClickListener((v: View) => {
Utils.positionToast(Toast.makeText(ProfileManagerActivity.this, R.string.qrcode, Toast.LENGTH_SHORT), qrcode,
getWindow, 0, Utils.dpToPx(ProfileManagerActivity.this, 8)).show
true
})
}
def bind(item: Profile) {
text.setText(item.name)
text.setChecked(item.id == ShadowsocksApplication.profileId)
this.item = item
}
def onClick(v: View) = {
ShadowsocksApplication.profileId(item.id)
finish
}
}
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
private val recycleBin = new ArrayBuffer[(Int, Profile)]
private var profiles = new ArrayBuffer[Profile]
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
def getItemCount = profiles.length
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i))
def onCreateViewHolder(vg: ViewGroup, i: Int) =
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
def add(item: Profile) {
removedSnackbar.dismiss
commitRemoves
val pos = getItemCount
if (ShadowsocksApplication.profileManager.createOrUpdateProfile(item)) {
profiles += item
notifyItemInserted(pos)
}
}
def remove(pos: Int) {
recycleBin.append((pos, profiles(pos)))
profiles.remove(pos)
notifyItemRemoved(pos)
}
def undoRemoves {
for ((index, item) <- recycleBin.reverseIterator) {
profiles.insert(index, item)
notifyItemInserted(index)
}
recycleBin.clear
}
def commitRemoves {
for ((index, item) <- recycleBin) {
ShadowsocksApplication.profileManager.delProfile(item.id)
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
}
recycleBin.clear
}
}
private lazy val profilesAdapter = new ProfilesAdapter
private var removedSnackbar: Snackbar = _
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_profiles)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.profiles)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha)
toolbar.setNavigationOnClickListener((v: View) => {
val intent = getParentActivityIntent
if (intent == null) finish else navigateUpTo(intent)
})
toolbar.inflateMenu(R.menu.add_profile_methods)
toolbar.setOnMenuItemClickListener(this)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
profilesList.setLayoutManager(new LinearLayoutManager(this))
profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter)
removedSnackbar = Snackbar.make(findViewById(android.R.id.content), R.string.removed, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
def onViewAttachedToWindow(v: View) = ()
})
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
profilesAdapter.remove(viewHolder.getAdapterPosition)
removedSnackbar.show
}
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
}).attachToRecyclerView(profilesList)
}
override def onDestroy {
super.onDestroy
profilesAdapter.commitRemoves
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (scanResult != null) Parser.parse(scanResult.getContents) match {
case Some(profile) => profilesAdapter.add(profile)
case _ => // ignore
}
}
def onMenuItemClick(item: MenuItem) = item.getItemId match {
case R.id.scan_qr_code =>
val integrator = new IntentIntegrator(this)
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
list.add("tw.com.quickmark")
integrator.setTargetApplications(list)
integrator.initiateScan()
true
case R.id.manual_settings =>
ShadowsocksApplication.profileManager.reload(-1)
ShadowsocksApplication.profileId(ShadowsocksApplication.profileManager.save.id)
finish
true
case _ => false
}
}
@@ -45,35 +45,28 @@ import java.util.Locale
import android.app.backup.BackupManager
import android.app.{Activity, AlertDialog, ProgressDialog}
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.content.res.AssetManager
import android.graphics.{Bitmap, Color, Typeface}
import android.net.{Uri, VpnService}
import android.os._
import android.preference.{Preference, PreferenceManager, SwitchPreference}
import android.support.v4.content.ContextCompat
import android.preference.{Preference, SwitchPreference}
import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.util.{DisplayMetrics, Log}
import android.util.Log
import android.view.{View, ViewGroup, ViewParent}
import android.webkit.{WebView, WebViewClient}
import android.widget._
import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database._
import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.utils._
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
import com.google.android.gms.analytics.HitBuilders
import com.google.zxing.integration.android.IntentIntegrator
import com.joanzapata.android.iconify.IconDrawable
import com.joanzapata.android.iconify.Iconify.IconValue
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import net.glxn.qrgen.android.QRCode
import net.simonvt.menudrawer.MenuDrawer
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@@ -86,7 +79,7 @@ class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout:
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
val text = imageUri.substring(Scheme.PROFILE.length)
val size = Utils.dpToPx(context, 16).toInt
val size = Utils.dpToPx(context, 16)
val idx = text.getBytes.last % 6
val color = Seq(Color.MAGENTA, Color.GREEN, Color.YELLOW, Color.BLUE, Color.DKGRAY, Color.CYAN)(
idx)
@@ -126,12 +119,10 @@ object Shadowsocks {
val REQUEST_CONNECT = 1
val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey,
Key.encMethod)
val FEATRUE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps,
Key.isUdpDns, Key.isAuth, Key.isIpv6, Key.isAutoConnect)
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS)
val PROXY_PREFS = Array(Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod)
val FEATURE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps, Key.isUdpDns, Key.isAuth, Key.isIpv6)
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
Executable.TUN2SOCKS)
// Helper functions
def updateDropDownPreference(pref: Preference, value: String) {
@@ -148,18 +139,13 @@ object Shadowsocks {
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
}
def updateProfileEditTextPreference(pref: Preference, value: String) {
pref.asInstanceOf[ProfileEditTextPreference].resetSummary(value)
pref.asInstanceOf[ProfileEditTextPreference].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 => updateProfileEditTextPreference(pref, profile.name)
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
case Key.remotePort => updateSummaryEditTextPreference(pref, profile.remotePort.toString)
case Key.localPort => updateSummaryEditTextPreference(pref, profile.localPort.toString)
@@ -176,14 +162,8 @@ object Shadowsocks {
}
class Shadowsocks
extends AppCompatActivity
with MenuAdapter.MenuListener {
// Flags
val MSG_CRASH_RECOVER: Int = 1
val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer"
val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId"
var singlePane: Int = -1
extends AppCompatActivity {
import Shadowsocks._
// Variables
private var serviceStarted = false
@@ -217,8 +197,8 @@ class Shadowsocks
if (fab != null) fab.setEnabled(true)
stateUpdate()
if (!status.getBoolean(getVersionName, false)) {
status.edit.putBoolean(getVersionName, true).apply()
if (!status.getBoolean(ShadowsocksApplication.getVersionName, false)) {
status.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
recovery()
}
}
@@ -236,33 +216,12 @@ class Shadowsocks
private lazy val preferences =
getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings]
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
private lazy val preferenceReceiver = new PreferenceBroadcastReceiver
private lazy val drawer = MenuDrawer.attach(this)
private lazy val menuAdapter = new MenuAdapter(this, getMenuList)
private lazy val listView = new ListView(this)
private lazy val profileManager =
new ProfileManager(settings, getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
private lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700)
private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_600)
var handler = new Handler()
def isSinglePane: Boolean = {
if (singlePane == -1) {
val metrics = new DisplayMetrics()
getWindowManager.getDefaultDisplay.getMetrics(metrics)
val widthPixels = metrics.widthPixels
val scaleFactor = metrics.density
val widthDp = widthPixels / scaleFactor
singlePane = if (widthDp <= 720) 1 else 0
}
singlePane == 1
}
private def changeSwitch(checked: Boolean) {
serviceStarted = checked
fab.setImageResource(if (checked) R.drawable.ic_cloud else R.drawable.ic_cloud_off)
@@ -359,18 +318,6 @@ class Shadowsocks
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
}
private def getVersionName: String = {
var version: String = null
try {
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
}
version
}
def isTextEmpty(s: String, msg: String): Boolean = {
if (s != null && s.length > 0) return false
Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).show
@@ -413,74 +360,58 @@ class Shadowsocks
}
}
def initAdView() {
if (settings.getString(Key.proxy, "") == "198.199.101.152") {
val layoutView = {
if (Build.VERSION.SDK_INT > 10) {
drawer.getContentContainer.getChildAt(0)
} else {
getLayoutView(drawer.getContentContainer.getParent)
}
}
if (layoutView != null) {
val adView = new AdView(this)
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
adView.setAdSize(AdSize.SMART_BANNER)
layoutView.asInstanceOf[ViewGroup].addView(adView, 0)
adView.loadAd(new AdRequest.Builder().build())
}
}
}
override def setContentView(layoutResId: Int) {
drawer.setContentView(layoutResId)
initAdView()
onContentChanged()
}
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main)
if (ShadowsocksApplication.proxy == "198.199.101.152") {
val adView = new AdView(this)
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
adView.setAdSize(AdSize.SMART_BANNER)
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 0) // TODO: test
adView.loadAd(new AdRequest.Builder().build())
}
// Update the profile
if (!status.getBoolean(getVersionName, false)) {
currentProfile = profileManager.create()
if (!status.getBoolean(ShadowsocksApplication.getVersionName, false)) {
currentProfile = ShadowsocksApplication.profileManager.create()
}
// Initialize the profile
currentProfile = {
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
}
// Initialize drawer
menuAdapter.setActiveId(settings.getInt(Key.profileId, -1))
menuAdapter.setListener(this)
listView.setAdapter(menuAdapter)
drawer.setMenuView(listView)
if (Utils.isLollipopOrAbove) {
drawer.setDrawerIndicatorEnabled(false)
} else {
// The drawable that replaces the up indicator in the action bar
drawer.setSlideDrawable(R.drawable.ic_drawer)
// Whether the previous drawable should be shown
drawer.setDrawerIndicatorEnabled(true)
}
if (!isSinglePane) {
drawer.openMenu(false)
ShadowsocksApplication.currentProfile getOrElse currentProfile
}
// Initialize action bar
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(getTitle)
toolbar.setTitle(getString(R.string.screen_name))
toolbar.setTitleTextAppearance(toolbar.getContext, R.style.Toolbar_Logo)
val field = classOf[Toolbar].getDeclaredField("mTitleTextView")
field.setAccessible(true)
val title: TextView = field.get(toolbar).asInstanceOf[TextView]
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf)
title.setOnClickListener((view: View) => {
ShadowsocksApplication.track(TAG, "about")
showAbout()
})
title.setOnLongClickListener((v: View) => {
if (Utils.isLollipopOrAbove && bgService != null
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
status.edit().putBoolean(Key.isNAT, natEnabled).apply()
deattachService()
attachService()
if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
.show()
true
} else {
false
}
})
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
fab.setOnClickListener((v: View) => {
@@ -503,29 +434,9 @@ class Shadowsocks
})
fab.setOnLongClickListener((v: View) => {
Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect,
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8).toInt).show
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
true
})
toolbar.setNavigationIcon(R.drawable.ic_drawer)
toolbar.setNavigationOnClickListener((v: View) => drawer.toggleMenu())
title.setOnLongClickListener((v: View) => {
if (Utils.isLollipopOrAbove && bgService != null
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
status.edit().putBoolean(Key.isNAT, natEnabled).apply()
deattachService()
attachService()
if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
.show()
true
} else {
false
}
})
// Register broadcast receiver
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
// Bind to the service
handler.post(() => {
@@ -555,222 +466,29 @@ class Shadowsocks
}
}
override def onRestoreInstanceState(inState: Bundle) {
super.onRestoreInstanceState(inState)
drawer.restoreState(inState.getParcelable(STATE_MENUDRAWER))
}
override def onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(STATE_MENUDRAWER, drawer.saveState())
outState.putInt(STATE_ACTIVE_VIEW_ID, currentProfile.id)
}
override def onBackPressed() {
val drawerState = drawer.getDrawerState
if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) {
drawer.closeMenu()
return
}
super.onBackPressed()
}
override def onActiveViewChanged(v: View, pos: Int) {
drawer.setActiveView(v, pos)
}
def newProfile(id: Int) {
val builder = new AlertDialog.Builder(this)
builder
.setTitle(R.string.add_profile)
.setItems(R.array.add_profile_methods, ((dialog: DialogInterface, which: Int) => which match {
case 0 =>
dialog.dismiss()
val h = showProgress(R.string.loading)
h.postDelayed(() => {
val integrator = new IntentIntegrator(Shadowsocks.this)
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
list.add("tw.com.quickmark")
integrator.setTargetApplications(list)
integrator.initiateScan()
h.sendEmptyMessage(0)
}, 600)
case 1 =>
dialog.dismiss()
addProfile(id)
case _ =>
}): DialogInterface.OnClickListener)
builder.create().show()
}
def reloadProfile() {
drawer.closeMenu(true)
currentProfile = ShadowsocksApplication.currentProfile match {
case Some(profile) => profile // updated
case None => // removed
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
ShadowsocksApplication.profileId(if (profiles.isEmpty) -1 else profiles.head.id)
ShadowsocksApplication.currentProfile getOrElse currentProfile
}
val h = showProgress(R.string.loading)
handler.postDelayed(() => {
currentProfile = {
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
}
menuAdapter.updateList(getMenuList, currentProfile.id)
updatePreferenceScreen()
h.sendEmptyMessage(0)
}, 600)
updatePreferenceScreen()
}
def addProfile(profile: Profile) {
drawer.closeMenu(true)
currentProfile = profile
ShadowsocksApplication.profileManager.createOrUpdateProfile(currentProfile)
ShadowsocksApplication.profileManager.reload(currentProfile.id)
val h = showProgress(R.string.loading)
handler.postDelayed(() => {
currentProfile = profile
profileManager.createOrUpdateProfile(currentProfile)
profileManager.reload(currentProfile.id)
menuAdapter.updateList(getMenuList, currentProfile.id)
updatePreferenceScreen()
h.sendEmptyMessage(0)
}, 600)
}
def addProfile(id: Int) {
drawer.closeMenu(true)
val h = showProgress(R.string.loading)
handler.postDelayed(() => {
currentProfile = profileManager.reload(id)
profileManager.save()
menuAdapter.updateList(getMenuList, currentProfile.id)
updatePreferenceScreen()
h.sendEmptyMessage(0)
}, 600)
}
def updateProfile(id: Int) {
drawer.closeMenu(true)
val h = showProgress(R.string.loading)
handler.postDelayed(() => {
currentProfile = profileManager.reload(id)
menuAdapter.setActiveId(id)
menuAdapter.notifyDataSetChanged()
updatePreferenceScreen()
h.sendEmptyMessage(0)
}, 600)
}
def delProfile(id: Int): Boolean = {
drawer.closeMenu(true)
val profile = profileManager.getProfile(id)
if (profile.isEmpty) return false
new AlertDialog.Builder(this)
.setMessage(String.format(Locale.ENGLISH, getString(R.string.remove_profile), profile.get.name))
.setCancelable(false)
.setNegativeButton(R.string.no,
((dialog: DialogInterface, i: Int) => dialog.cancel()): DialogInterface.OnClickListener)
.setPositiveButton(R.string.yes, ((dialog: DialogInterface, i: Int) => {
profileManager.delProfile(id)
val profileId = {
val profiles = profileManager.getAllProfiles.getOrElse(List[Profile]())
if (profiles.isEmpty) -1 else profiles.head.id
}
currentProfile = profileManager.load(profileId)
menuAdapter.updateList(getMenuList, currentProfile.id)
updatePreferenceScreen()
dialog.dismiss()
}): DialogInterface.OnClickListener)
.create()
.show()
true
}
def getProfileList: List[Item] = {
val list = profileManager.getAllProfiles getOrElse List[Profile]()
list.map(p => new IconItem(p.id, p.name, -1, updateProfile, delProfile))
}
def getMenuList: List[Any] = {
val buf = new ListBuffer[Any]()
buf += new Category(getString(R.string.profiles))
buf ++= getProfileList
buf +=
new DrawableItem(-400, getString(R.string.add_profile), new IconDrawable(this, IconValue.fa_plus_circle)
.colorRes(android.R.color.darker_gray).sizeDp(26), newProfile)
buf += new Category(getString(R.string.settings))
buf += new DrawableItem(-100, getString(R.string.recovery), new IconDrawable(this, IconValue.fa_recycle)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG)
.setAction("reset")
.setLabel(getVersionName)
.build())
recovery()
})
buf +=
new DrawableItem(-200, getString(R.string.flush_dnscache), new IconDrawable(this, IconValue.fa_refresh)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG)
.setAction("flush_dnscache")
.setLabel(getVersionName)
.build())
flushDnsCache()
})
buf +=
new DrawableItem(-300, getString(R.string.qrcode), new IconDrawable(this, IconValue.fa_qrcode)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG)
.setAction("qrcode")
.setLabel(getVersionName)
.build())
showQrCode()
})
buf += new DrawableItem(-400, getString(R.string.about), new IconDrawable(this, IconValue.fa_info_circle)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG)
.setAction("about")
.setLabel(getVersionName)
.build())
showAbout()
})
buf.toList
updatePreferenceScreen()
}
protected override def onPause() {
super.onPause()
ShadowsocksApplication.profileManager.save
prepared = false
}
@@ -778,19 +496,19 @@ class Shadowsocks
if (bgService != null) {
bgService.getState match {
case State.CONNECTING =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
fabProgressCircle.show()
changeSwitch(checked = true)
case State.CONNECTED =>
fab.setBackgroundTintList(greenTint);
fab.setBackgroundTintList(greenTint)
fabProgressCircle.hide()
changeSwitch(checked = true)
case State.STOPPING =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
fabProgressCircle.show()
changeSwitch(checked = false)
case _ =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
fabProgressCircle.hide()
changeSwitch(checked = false)
}
@@ -803,10 +521,8 @@ class Shadowsocks
stateUpdate()
ConfigUtils.refresh(this)
// Check if profile list changed
val id = settings.getInt(Key.profileId, -1)
if (id != -1 && id != currentProfile.id)
reloadProfile()
// Check if current profile changed
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
}
private def setPreferenceEnabled(enabled: Boolean) {
@@ -816,7 +532,7 @@ class Shadowsocks
pref.setEnabled(enabled)
}
}
for (name <- Shadowsocks.FEATRUE_PREFS) {
for (name <- Shadowsocks.FEATURE_PREFS) {
val pref = preferences.findPreference(name)
if (pref != null) {
if (Seq(Key.isGlobalProxy, Key.proxyedApps)
@@ -831,11 +547,12 @@ class Shadowsocks
private def updatePreferenceScreen() {
val profile = currentProfile
Shadowsocks.updatePreference(preferences.findPreference(Key.profileName), Key.profileName, profile)
for (name <- Shadowsocks.PROXY_PREFS) {
val pref = preferences.findPreference(name)
Shadowsocks.updatePreference(pref, name, profile)
}
for (name <- Shadowsocks.FEATRUE_PREFS) {
for (name <- Shadowsocks.FEATURE_PREFS) {
val pref = preferences.findPreference(name)
Shadowsocks.updatePreference(pref, name, profile)
}
@@ -853,7 +570,6 @@ class Shadowsocks
override def onDestroy() {
super.onDestroy()
deattachService()
unregisterReceiver(preferenceReceiver)
new BackupManager(this).dataChanged()
handler.removeCallbacksAndMessages(null)
}
@@ -881,13 +597,12 @@ class Shadowsocks
}
def reset() {
crashRecovery()
install()
}
private def recovery() {
def recovery() {
serviceStop()
val h = showProgress(R.string.recovering)
Future {
@@ -896,26 +611,7 @@ class Shadowsocks
}
}
private def dp2px(dp: Int): Int =
Math.round(dp * (getBaseContext.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
private def showQrCode() {
val image = new ImageView(this)
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
val qrcode = QRCode.from(Parser.generate(currentProfile))
.withSize(dp2px(250), dp2px(250)).asInstanceOf[QRCode]
image.setImageBitmap(qrcode.bitmap())
new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(getString(R.string.close),
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
.setView(image)
.create()
.show()
}
private def flushDnsCache() {
def flushDnsCache() {
val h = showProgress(R.string.flushing)
Future {
Utils.toggleAirplaneMode(getBaseContext)
@@ -923,23 +619,13 @@ class Shadowsocks
}
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (scanResult != null) {
Parser.parse(scanResult.getContents) match {
case Some(profile) => addProfile(profile)
case _ => // ignore
}
} else {
resultCode match {
case Activity.RESULT_OK =>
prepared = true
serviceStart()
case _ =>
cancelStart()
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
}
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match {
case Activity.RESULT_OK =>
prepared = true
serviceStart()
case _ =>
cancelStart()
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
}
var isRoot: Option[Boolean] = None
@@ -953,12 +639,12 @@ class Shadowsocks
}
def checkText(key: String): Boolean = {
val text = settings.getString(key, "")
val text = ShadowsocksApplication.settings.getString(key, "")
!isTextEmpty(text, getString(R.string.proxy_empty))
}
def checkNumber(key: String, low: Boolean): Boolean = {
val text = settings.getString(key, "")
val text = ShadowsocksApplication.settings.getString(key, "")
if (isTextEmpty(text, getString(R.string.port_empty))) return false
try {
val port: Int = Integer.valueOf(text)
@@ -978,7 +664,7 @@ class Shadowsocks
/** Called when connect button is clicked. */
def serviceStart() {
bgService.start(ConfigUtils.load(settings))
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
if (isVpnEnabled) {
changeSwitch(checked = false)
@@ -986,7 +672,6 @@ class Shadowsocks
}
private def showAbout() {
val web = new WebView(this)
web.loadUrl("file:///android_asset/pages/about.html")
web.setWebViewClient(new WebViewClient() {
@@ -996,18 +681,10 @@ class Shadowsocks
}
})
var versionName = ""
try {
versionName = getPackageManager.getPackageInfo(getPackageName, 0).versionName
} catch {
case ex: PackageManager.NameNotFoundException =>
versionName = ""
}
new AlertDialog.Builder(this)
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, versionName))
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, ShadowsocksApplication.getVersionName))
.setCancelable(false)
.setNegativeButton(getString(R.string.ok_iknow),
.setNegativeButton(getString(android.R.string.ok),
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
.setView(web)
.create()
@@ -1026,13 +703,13 @@ class Shadowsocks
handler.post(() => if (state != s) {
s match {
case State.CONNECTING =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
fab.setImageResource(R.drawable.ic_cloud_queue)
fab.setEnabled(false)
fabProgressCircle.show()
setPreferenceEnabled(enabled = false)
case State.CONNECTED =>
fab.setBackgroundTintList(greenTint);
fab.setBackgroundTintList(greenTint)
if (state == State.CONNECTING) {
fabProgressCircle.beginFinalAnimation()
} else {
@@ -1042,7 +719,7 @@ class Shadowsocks
changeSwitch(checked = true)
setPreferenceEnabled(enabled = false)
case State.STOPPED =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
handler.postDelayed(() => fabProgressCircle.hide(), 1000)
fab.setEnabled(true)
changeSwitch(checked = false)
@@ -1050,7 +727,7 @@ class Shadowsocks
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show
setPreferenceEnabled(enabled = true)
case State.STOPPING =>
fab.setBackgroundTintList(greyTint);
fab.setBackgroundTintList(greyTint)
fab.setImageResource(R.drawable.ic_cloud_queue)
fab.setEnabled(false)
fabProgressCircle.show()
@@ -1059,12 +736,4 @@ class Shadowsocks
state = s
})
}
class PreferenceBroadcastReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent) {
currentProfile = profileManager.save()
menuAdapter.updateList(getMenuList, currentProfile.id)
}
}
}
@@ -43,19 +43,47 @@ import java.util
import java.util.concurrent.TimeUnit
import android.app.Application
import com.github.shadowsocks.database.DBHelper
import com.github.shadowsocks.utils.Utils
import com.google.android.gms.analytics.GoogleAnalytics
import android.content.pm.PackageManager
import android.preference.PreferenceManager
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
import com.github.shadowsocks.utils.{Key, Utils}
import com.google.android.gms.analytics.{HitBuilders, GoogleAnalytics}
import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
object ShadowsocksApplication {
var instance: ShadowsocksApplication = _
lazy val dbHelper = new DBHelper(instance)
final val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = _
lazy val tracker = GoogleAnalytics.getInstance(instance).newTracker(R.xml.tracker)
lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance)
lazy val profileManager = new ProfileManager(settings, dbHelper)
def getVersionName = try {
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
} catch {
case _: PackageManager.NameNotFoundException => "Package name not found"
case _: Throwable => null
}
// send event
def track(category: String, action: String) = tracker.send(new HitBuilders.EventBuilder()
.setAction(action)
.setLabel(getVersionName)
.build())
def profileId = settings.getInt(Key.profileId, -1)
def profileId(i: Int) = settings.edit.putInt(Key.profileId, i).apply
def proxy = settings.getString(Key.proxy, "")
def currentProfile = profileManager.getProfile(profileId)
}
class ShadowsocksApplication extends Application {
lazy val dbHelper = new DBHelper(this)
lazy val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = null
lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
import ShadowsocksApplication._
override def onCreate() {
ShadowsocksApplication.instance = this
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
@@ -40,22 +40,20 @@
package com.github.shadowsocks
import java.io.File
import java.lang.reflect.{InvocationTargetException, Method}
import java.net.{Inet6Address, InetAddress}
import java.util.Locale
import android.app._
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.net.{Network, ConnectivityManager}
import android.net.{ConnectivityManager, Network}
import android.os._
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
import android.util.{SparseArray, Log}
import android.util.{Log, SparseArray}
import android.widget.Toast
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.HitBuilders
import scala.collection._
import scala.collection.mutable.ArrayBuffer
@@ -77,8 +75,6 @@ class ShadowsocksNatService extends Service with BaseService {
var apps: Array[ProxiedApp] = null
val myUid = Process.myUid()
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
private val dnsAddressCache = new SparseArray[String]
def getNetId(network: Network): Int = {
@@ -254,8 +250,8 @@ class ShadowsocksNatService extends Service with BaseService {
def startDnsDaemon() {
val conf = if (config.route == Route.BYPASS_CHN) {
val reject = ConfigUtils.getRejectList(getContext, application)
val blackList = ConfigUtils.getBlackList(getContext, application)
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "127.0.0.1", 8153,
Path.BASE + "pdnsd-nat.pid", reject, blackList, 8163, "")
} else {
@@ -457,19 +453,14 @@ class ShadowsocksNatService extends Service with BaseService {
registerReceiver(lockReceiver, screenFilter)
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("start")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "start")
changeState(State.CONNECTING)
Future {
if (config.proxy == "198.199.101.152") {
val holder = application.containerHolder
val holder = ShadowsocksApplication.containerHolder
try {
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
@@ -531,12 +522,7 @@ class ShadowsocksNatService extends Service with BaseService {
}
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("stop")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "stop")
// reset NAT
killProcesses()
@@ -1,12 +1,24 @@
package com.github.shadowsocks
import android.os.Bundle
import android.preference.PreferenceFragment
import android.preference.{Preference, PreferenceFragment}
// TODO: Move related logic here
class ShadowsocksSettings extends PreferenceFragment {
private lazy val activity = getActivity.asInstanceOf[Shadowsocks]
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_all)
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "reset")
activity.recovery()
true
})
findPreference("flush_dnscache").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache")
activity.flushDnsCache()
true
})
}
}
@@ -53,7 +53,6 @@ import android.util.Log
import android.widget.Toast
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.HitBuilders
import org.apache.commons.net.util.SubnetUtils
import scala.collection.mutable
@@ -62,8 +61,6 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
class ShadowsocksVpnService extends VpnService with BaseService {
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
val TAG = "ShadowsocksVpnService"
val VPN_MTU = 1500
val PRIVATE_VLAN = "26.26.26.%s"
@@ -152,12 +149,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
// channge the state
changeState(State.STOPPING)
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("stop")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "stop")
// reset VPN
killProcesses()
@@ -231,18 +223,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
return
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("start")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "start")
changeState(State.CONNECTING)
Future {
if (config.proxy == "198.199.101.152") {
val holder = getApplication.asInstanceOf[ShadowsocksApplication].containerHolder
val holder = ShadowsocksApplication.containerHolder
try {
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
@@ -371,8 +358,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val ipv6 = if (config.isIpv6) "" else "reject = ::/0;"
val conf = {
if (config.route == Route.BYPASS_CHN) {
val reject = ConfigUtils.getRejectList(getContext, application)
val blackList = ConfigUtils.getBlackList(getContext, application)
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153,
Path.BASE + "pdnsd-vpn.pid", reject, blackList, 8163, ipv6)
} else {
@@ -1,179 +0,0 @@
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2014 <max.c.lv@gmail.com>
*
* 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/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.database
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{BaseAdapter, TextView}
import com.github.shadowsocks.R
abstract class Item {
def id: Int
def title: String
def click: Int => Unit
def longClick: Int => Boolean
}
case class IconItem(id: Int, title: String, iconRes: Int,
click: Int => Unit, longClick: Int => Boolean = _ => false)
extends Item
case class DrawableItem(id: Int, title: String, iconRes: Drawable,
click: Int => Unit, longClick: Int => Boolean = _ => false)
extends Item
case class Category(title: String)
object MenuAdapter {
trait MenuListener {
def onActiveViewChanged(v: View, pos: Int)
}
val UNDEFINED = -1
val CATEGORY = 0
val ITEM = 1
}
class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter {
def setListener(listener: MenuAdapter.MenuListener) {
this.listener = listener
}
def setActiveId(activeId: Int) {
this.activeId = activeId
}
@Override def getCount: Int = {
items.size
}
@Override def getItem(position: Int): Object = {
items(position).asInstanceOf[Object]
}
@Override def getItemId(position: Int): Long = {
position
}
@Override override def getItemViewType(position: Int): Int = {
getItem(position) match {
case Category => MenuAdapter.CATEGORY
case IconItem => MenuAdapter.ITEM
case DrawableItem => MenuAdapter.ITEM
case _ => MenuAdapter.UNDEFINED
}
}
@Override override def getViewTypeCount: Int = {
3
}
@Override override def isEnabled(position: Int): Boolean = {
getItemViewType(position) != MenuAdapter.UNDEFINED
}
@Override override def areAllItemsEnabled: Boolean = {
false
}
@Override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
var v: View = convertView
val item = getItem(position)
item match {
case value: Category =>
if (v == null) {
v = LayoutInflater.from(context).inflate(R.layout.menu_row_category, parent, false)
}
v.asInstanceOf[TextView].setText(value.title)
case value: Item =>
if (v == null) {
v = LayoutInflater.from(context).inflate(R.layout.menu_row_item, parent, false)
}
val tv: TextView = v.asInstanceOf[TextView]
tv.setText(value.title)
value match {
case iconItem: IconItem =>
if (iconItem.iconRes != -1) {
tv.setCompoundDrawablesWithIntrinsicBounds(iconItem.iconRes, 0, 0, 0)
}
case drawableItem: DrawableItem =>
if (drawableItem.iconRes != null) {
tv.setCompoundDrawablesWithIntrinsicBounds(drawableItem.iconRes, null, null, null)
}
}
tv.setOnClickListener((view: View) => {
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
item.click(item.id)
})
tv.setOnLongClickListener((view: View) => {
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
item.longClick(item.id)
})
v.setTag(R.id.mdItem, value)
if (value.id == activeId) {
if (listener != null) listener.onActiveViewChanged(v, position)
}
case _ =>
}
v.setTag(R.id.mdActiveViewPosition, position)
v
}
def updateList(list: List[Any], activeId: Int) {
items = list
this.activeId = activeId
notifyDataSetChanged()
}
private var listener: MenuAdapter.MenuListener = null
private var activeId: Int = -1
}
@@ -1,78 +0,0 @@
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* 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/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.preferences
import android.content.{Intent, Context}
import android.preference.EditTextPreference
import android.util.AttributeSet
import com.github.shadowsocks.utils.Action
class ProfileEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
extends EditTextPreference(context, attrs, defStyle) {
def this(context: Context, attrs: AttributeSet) = {
this(context, attrs, android.R.attr.editTextPreferenceStyle)
mDefaultSummary = getSummary
}
override def setText(text: String) {
super.setText(text)
setSummary(text)
}
override def setSummary(summary: CharSequence) {
if (summary.toString.isEmpty) {
super.setSummary(mDefaultSummary)
} else {
super.setSummary(summary)
}
context.sendBroadcast(new Intent(Action.UPDATE_PREFS))
}
def resetSummary(summary: CharSequence) {
if (summary.toString.isEmpty) {
super.setSummary(mDefaultSummary)
} else {
super.setSummary(summary)
}
}
private var mDefaultSummary: CharSequence = getSummary
}
@@ -198,14 +198,14 @@ object ConfigUtils {
}
def refresh(context: Context) {
val holder = context.getApplicationContext.asInstanceOf[ShadowsocksApplication].containerHolder
val holder = ShadowsocksApplication.containerHolder
if (holder != null) holder.refresh()
}
def getRejectList(context: Context, app: ShadowsocksApplication): String = {
def getRejectList(context: Context): String = {
val default = context.getString(R.string.reject)
try {
val container = app.containerHolder.getContainer
val container = ShadowsocksApplication.containerHolder.getContainer
val update = container.getString("reject")
if (update == null || update.isEmpty) default else update
} catch {
@@ -213,10 +213,10 @@ object ConfigUtils {
}
}
def getBlackList(context: Context, app: ShadowsocksApplication): String = {
def getBlackList(context: Context): String = {
val default = context.getString(R.string.black_list)
try {
val container = app.containerHolder.getContainer
val container = ShadowsocksApplication.containerHolder.getContainer
val update = container.getString("black_list")
if (update == null || update.isEmpty) default else update
} catch {
@@ -111,8 +111,6 @@ object State {
object Action {
val SERVICE = "com.github.shadowsocks.SERVICE"
val CLOSE = "com.github.shadowsocks.CLOSE"
val UPDATE_FRAGMENT = "com.github.shadowsocks.ACTION_UPDATE_FRAGMENT"
val UPDATE_PREFS = "com.github.shadowsocks.ACTION_UPDATE_PREFS"
}
object Route {
@@ -92,6 +92,9 @@ object Utils {
new String(Base64.encode(mdg.digest, 0))
}
def dpToPx(context: Context, dp: Int): Int =
Math.round(dp * (context.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
/*
* round or floor depending on whether you are using offsets(floor) or
* widths(round)