From 53fcba30b03ef194e1df81a0d2875497342b34a8 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 22 Sep 2013 10:52:44 +0800 Subject: [PATCH 1/6] WiP, drawer works now --- build.sbt | 10 +++- project/Build.scala | 8 +-- project/plugins.sbt | 2 +- src/main/res/drawable-hdpi/ic_drawer.png | Bin 0 -> 963 bytes src/main/res/drawable-mdpi/ic_drawer.png | Bin 0 -> 955 bytes src/main/res/drawable-xhdpi/ic_drawer.png | Bin 0 -> 979 bytes src/main/res/layout/layout_switch.xml | 3 +- .../com/github/shadowsocks/Shadowsocks.scala | 52 +++++++++++++----- .../shadowsocks/database/DBHelper.scala | 22 ++++++++ .../github/shadowsocks/database/Profile.scala | 47 ++++++++++++++++ 10 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_drawer.png create mode 100644 src/main/res/drawable-mdpi/ic_drawer.png create mode 100644 src/main/res/drawable-xhdpi/ic_drawer.png create mode 100644 src/main/scala/com/github/shadowsocks/database/DBHelper.scala create mode 100644 src/main/scala/com/github/shadowsocks/database/Profile.scala diff --git a/build.sbt b/build.sbt index db33d8df..0a01f5e0 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,6 @@ libraryDependencies ++= Seq( "com.google.android" % "support-v4" % "r12", "com.google.android.analytics" % "analytics" % "2.0beta4", "dnsjava" % "dnsjava" % "2.1.5", - "com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4", "com.google.android.admob" % "admob" % "6.3.1" ) @@ -10,6 +9,13 @@ libraryDependencies ++= Seq( "com.actionbarsherlock" % "actionbarsherlock" % "4.4.0" artifacts(Artifact("actionbarsherlock", "apklib", "apklib")), "net.saik0.android.unifiedpreference" % "unifiedpreference" % "0.0.2" artifacts(Artifact("unifiedpreference", "apklib", "apklib")), "org.jraf" % "android-switch-backport" % "1.0" artifacts(Artifact("android-switch-backport", "apklib", "apklib")), - "de.keyboardsurfer.android.widget" % "crouton" % "1.7" + "net.simonvt.menudrawer" % "menudrawer" % "3.0.4" artifacts(Artifact("menudrawer", "apklib", "apklib")) +) + +libraryDependencies ++= Seq( + "de.keyboardsurfer.android.widget" % "crouton" % "1.7", + "com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4", + "com.j256.ormlite" % "ormlite-core" % "4.47", + "com.j256.ormlite" % "ormlite-android" % "4.47" ) diff --git a/project/Build.scala b/project/Build.scala index 822738a0..55aa12fb 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -5,8 +5,8 @@ import sbtandroid._ import sbtandroid.AndroidPlugin._ object App { - val version = "1.8.3" - val versionCode = 48 + val version = "1.9.0" + val versionCode = 50 } object General { @@ -28,6 +28,7 @@ object General { "-keep interface com.actionbarsherlock.** { *; }", "-keep class org.jraf.android.backport.** { *; }", "-keep class com.github.shadowsocks.** { *; }", + "-keep class * extends com.j256.ormlite.** { *; }", "-keepattributes *Annotation*") val proguardSettings = Seq ( @@ -36,8 +37,7 @@ object General { ) val miscSettings = Seq ( - cachePasswords := true, - publicServer := "127.0.0.1" + cachePasswords := true ) lazy val fullAndroidSettings = diff --git a/project/plugins.sbt b/project/plugins.sbt index b17b415f..3bbacfde 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,4 +6,4 @@ resolvers += Resolver.url("madeye private releases", new URL("http://madeye-mave addSbtPlugin("org.scala-sbt" % "sbt-android" % "0.7.1-SNAPSHOT") -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.4.0") +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1") diff --git a/src/main/res/drawable-hdpi/ic_drawer.png b/src/main/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 0000000000000000000000000000000000000000..ec8c51f87f7db9cc7556a95a757716b114a51dda GIT binary patch literal 963 zcmaJ=zi-n(78Qu~a3!A@8Z z8$$;E0))CSF|sfe7Nm}>ObiTcObDbRm2;Y;3;!>rf# z^akCpr)%{x{eOwoUv%3f)i!D3E{UxWF*^?KAW-wH9%>-V89h2dWroSHZmUh&=6%J+ z9%rQ(F7X1&W|(p%2`qbn2#OYdcgHMn9hH5MH=!A5sPFELL)0Aax9su2E;(#v z8it{W9ExmD%&R{PfeL@`Sd z*=XbwE6MuN)?7hHksZ1Laj_3lMXQ5{L}h8D=P7u>Le`IFWugV+6D!~a4yGy1fu{L? zsOK%v5ow@{c>gIJwMGHr8z{oV(58*+Zl$gQMGKKdaM;55a9+iF9}^t)aR9Vt9+;Nx z`l&s|o2F9pBVzeBs_80A6*$**6eC}hBq-;0Ww9y#wWaY}n%U5@gPoAD0e0%u5 u__g`z^`)atPdb`>{LnXUPBI(JDrB~wT)lnz;^*7+S=Flh`kUQ@XMX?__$)^N literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_drawer.png b/src/main/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 0000000000000000000000000000000000000000..7420a68df97b5f14f3c4256f29e8658f2218ad03 GIT binary patch literal 955 zcmaJ=O^ee&7!KBzW!b`l7e5XTSv&~aWRh*#ZD=gJN#hnwDQ(3bJUC6JZRjQwlc`O6 zS`|S-5zqbtFM?+eiU+}yHxHit2ZA2F2zAnJst0ReGV>14^Sqz)xKY2B-MFy9aa^`` zXf)Zrm8|tM?0@OU46*Gzt#)XO^ynCcnA>+q7lWFI`nZXaGr98-mpLxOyX_9`Sl3mX zcmhf=LhJ>M&2i;Q93Xp$Dd^(9>udbCH*b01IvRgdv7i;`c;FsRL)@Cy+xB#5mmI!w z6_jI@33!+y5PL^{q{bRQ*Hzg$xfXdahtQ$Mp9Iyh8bBu@28sZ4HY`9;kOXNrUn~}P zfD9!lLRpmLoK#eIWfelO_;}_mbb4yjFc-d&s(D28e1`kq;kC74gS2C+vT z_G!v>mfRv-$xTMV3z%mEhwfeMm?81NJZ05g9gDG2Z^3m|#!{%RfrYM-yA|FuK)l5 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_drawer.png b/src/main/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 0000000000000000000000000000000000000000..7ffbe5c507bafb3f08c25d37dd002c5e486557d4 GIT binary patch literal 979 zcmaJ=L2J}N7)>pdQf-e~dvQp?lbcMkn@uw|t!=Vt7fdPJ1s6eRHko$AHkmk?b<@R@ zhl1eMUr>+g#iIumLGUPg@<%9wH-$Rc?rwY78kl@D!+Y<0-^@2px3@NCXBK7zL71&> z+BF_QKBlMmKkfB?^KhM28f=H`v93q4uojRe2Gz*h$2IH)hYvsFWkHw}!+L`?+`Gsp zk>uqVDT@-$7KG*1Eb;s{W}u1p!`KvmzI`WxFfhe?nhV{;!mV(#NAXT?tM2#Oz7dG4 zD_}W8Tp+^C16gzsrzkVUzAobPd|MVlAHv$EcxtNQZUc)@3^WNAd{~B{tVl|+r0e=E zph5-8P?Z(6py;TmA_&3Y6L~fo?4g?N407?6DYh6(kSuq*U8!4?2;G+z!!U9URV{Et zAw7(lmlfi4aj0P9)Td#>LK1^q(QA?pGez$7w1g-b$;Rp6nD~LonU~0l1oM)HK-c|0 zG>S&(l-2N=-?74J{V>6D4X30-eSUF!i+QR9SrmJW&^jRp!&BUD5k}G$Nr1JZ0oU`x zIA8biu8XR1%DmXeRofJ~f)s`UD#CT$Hq@ncU3Cr#`r@XDF+i#xpC$Ukr*Xr5#=HHW()sJ^B-8`C@n*1SL z1uGUVy3X { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "home", getVersionName, 0L) + drawer.toggleMenu() + return true + } case 0 => { EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) recovery() diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala new file mode 100644 index 00000000..654c0acb --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -0,0 +1,22 @@ +package com.github.shadowsocks.database + +import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import com.j256.ormlite.support.ConnectionSource +import com.j256.ormlite.table.TableUtils +import com.j256.ormlite.dao.Dao + +class DBHelper(implicit val context: Context) extends OrmLiteSqliteOpenHelper(context, "postnauka", null, 3) { + + lazy val profileDao:Dao[Profile,Int] = getDao(classOf[Profile]) + + def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) { + TableUtils.createTable(connectionSource, classOf[Profile]) + } + + def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, newVersion: Int) { + TableUtils.dropTable(connectionSource, classOf[Profile], true) + onCreate(database, connectionSource) + } +} diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala new file mode 100644 index 00000000..b034aab0 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -0,0 +1,47 @@ +package com.github.shadowsocks.database + +import com.j256.ormlite.field.{DataType, DatabaseField} + +class Profile { + @DatabaseField(id = true) + var id: Int = 0 + + @DatabaseField + var name: String = null + + @DatabaseField(foreign = true, canBeNull = false) + var host: String = null + + @DatabaseField + var localPort: Int = 0 + + @DatabaseField + var remotePort: Int = 0 + + @DatabaseField + var password: String = null + + @DatabaseField + var method: String = null + + @DatabaseField + var date: String = null + + @DatabaseField + var upload: Int = 0 + + @DatabaseField + var download: Int = 0 + + @DatabaseField + var chnroute: Boolean = false + + @DatabaseField + var global: Boolean = false + + @DatabaseField(dataType = DataType.LONG_STRING) + var individual: String = null + + @DatabaseField(dataType = DataType.LONG_STRING) + var description: String = null +} From dfe737a8ebf1b4e449ee1e93c81062309de61c4e Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 28 Sep 2013 16:21:28 +0800 Subject: [PATCH 2/6] update menu --- .../md__category_background.9.png | Bin 0 -> 173 bytes .../drawable-hdpi/md__list_focused_holo.9.png | Bin 0 -> 191 bytes .../md__list_longpressed_holo.9.png | Bin 0 -> 154 bytes .../md__list_pressed_holo_dark.9.png | Bin 0 -> 159 bytes ...md__list_selector_disabled_holo_dark.9.png | Bin 0 -> 189 bytes .../md__category_background.9.png | Bin 0 -> 165 bytes .../drawable-mdpi/md__list_focused_holo.9.png | Bin 0 -> 171 bytes .../md__list_longpressed_holo.9.png | Bin 0 -> 151 bytes .../md__list_pressed_holo_dark.9.png | Bin 0 -> 158 bytes ...md__list_selector_disabled_holo_dark.9.png | Bin 0 -> 172 bytes .../md__category_background.9.png | Bin 0 -> 179 bytes .../md__list_focused_holo.9.png | Bin 0 -> 203 bytes .../md__list_longpressed_holo.9.png | Bin 0 -> 158 bytes .../md__list_pressed_holo_dark.9.png | Bin 0 -> 163 bytes ...md__list_selector_disabled_holo_dark.9.png | Bin 0 -> 190 bytes ...lector_background_transition_holo_dark.xml | 20 ++++ .../drawable/md__list_selector_holo_dark.xml | 27 +++++ src/main/res/layout/menu_row_category.xml | 5 + src/main/res/layout/menu_row_item.xml | 5 + src/main/res/values/colors.xml | 2 + src/main/res/values/ids.xml | 28 +++++ src/main/res/values/styles.xml | 31 +++++ .../com/github/shadowsocks/Shadowsocks.scala | 83 +++++++------ .../shadowsocks/database/MenuAdapter.scala | 113 ++++++++++++++++++ 24 files changed, 277 insertions(+), 37 deletions(-) create mode 100644 src/main/res/drawable-hdpi/md__category_background.9.png create mode 100644 src/main/res/drawable-hdpi/md__list_focused_holo.9.png create mode 100644 src/main/res/drawable-hdpi/md__list_longpressed_holo.9.png create mode 100644 src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png create mode 100644 src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png create mode 100644 src/main/res/drawable-mdpi/md__category_background.9.png create mode 100644 src/main/res/drawable-mdpi/md__list_focused_holo.9.png create mode 100644 src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png create mode 100644 src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png create mode 100644 src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png create mode 100644 src/main/res/drawable-xhdpi/md__category_background.9.png create mode 100644 src/main/res/drawable-xhdpi/md__list_focused_holo.9.png create mode 100644 src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png create mode 100644 src/main/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png create mode 100644 src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png create mode 100644 src/main/res/drawable/md__list_selector_background_transition_holo_dark.xml create mode 100644 src/main/res/drawable/md__list_selector_holo_dark.xml create mode 100644 src/main/res/layout/menu_row_category.xml create mode 100644 src/main/res/layout/menu_row_item.xml create mode 100644 src/main/res/values/ids.xml create mode 100644 src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala diff --git a/src/main/res/drawable-hdpi/md__category_background.9.png b/src/main/res/drawable-hdpi/md__category_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..43a20adcb19dd73d32f7555dfd96e5294800bc82 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^+(2x^!3HF|ZYnwhDVAa<&kznEsNqQI0P;BtJR*yM z>N-J~(fs7;wLn3CPZ!4!kIuJO4Y>{&@Hhvq{NlFv|INDNQ&?t6z4B$!f5v;Ln>B;0 zU-ImdKI;Vst0HI7c0ssI2 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/md__list_focused_holo.9.png b/src/main/res/drawable-hdpi/md__list_focused_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..555270842a739eb45d404ab473ad38a4c730240c GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr5#iE{-7*Q>RY2$k|}PUG3I8+hq?M&UySt({#?YOY9F7 zV)|4jusAQAw~U49z^wIm)#n)N-o9e&+IDtd30r~4TeZ_)uKQd*6*KFj^h@OhGw-CF i@bOgmX?1Mk5ox7hDXx8`mUDoXF?hQAxvX2?p zbb>IW`N`93fr2)kE{-7*QBRJJkl{&7FVVepxUS$Rt1LT2VAvn7lLLt0oo!v4%( tpjG6n=4tr&fORvYRP%q%M>EqU82;Q9of*be(FHVz!PC{xWt~$(699PnDoy|Z literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png b/src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..5654cd69429fd0a3502a05b5f827bffab89cc7e0 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhEX7WqAsj$Z!;#Vf2?p zbb>IW`N`93fr5^nE{-7*QD_LZ%D`aoRl?HIGCc%n7=x#)pUXO@geCyJM=ZAh literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fd30dcdc9c39c836e509486854f9da03486892 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^;y~=k!3HF){@Qy1DVAa<&kznEsNqQI0P;BtJR*x3 z7`Qt@n9=;?>9s(?cuyC{kcif|*A4j^95`Gq9-KV8B6o6ra`I!Z80pW;7d5zZGhRBg zm{FnhT6Xzih3ZMUGkfBc^kAT_eS7{3zPQ8d#DAm4nJ zaCd?*qxs3xYk`8ko-U3d5>tD79C;fY1elknGfSSZkgss!Fq+WV_wb)ql1{DGd2)`S!@o4yre42YU?3urKd Mr>mdKI;Vst081n_F8}}l literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png b/src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf8e03623c94b68d31963ffe7e59c72c3dcc059 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`86o-U3d5>t~CW>mH@a{g&g;s7!l1y4Byva-KaJLVIiI)OpBYodjS ofJx&-C1HsL_x|*Yp0_#7zz}{&aC*>dO_0?Lp00i_>zopr00f~Zw*UYD literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png b/src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6e77525d2dbbc1673145d60d775602c85264330d GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf4nJ zaCd?*qxs3xYk`6eo-U3d5>t~6?){p=$oZ!|i46oEul1LdSlBa@C*nY4{!0_J9HA2p woHspUxHx7V@R-QS6OjM!cw6swyHo~-e~lvMp^1B@1I=RaboFyt=akR{0Ay(_r~m)} literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..92da2f0dd3711a2ceb843768cafd6b91a2807b43 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^{6MVD!3HFkzrK_Oq*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMg#;uvX%d>fBtxy+SfFGJhTv9Z9>9n%%Tr7{oI+Q@DMao9hQt@O?p#8;eZd15IY| MboFyt=akR{068-?^#A|> literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/md__category_background.9.png b/src/main/res/drawable-xhdpi/md__category_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..942d72e65b095ad900722a8e5e7385228ae0c783 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^d_e5P!3HGLFFij7q*#ibJVQ8upoSx*1IXtr@Q5sC zVBqcqVMg-D=*+w0EX1GeE8VNeS)2#7n8DN4&t;ucLK6V`fk$Nk literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png b/src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png new file mode 100644 index 0000000000000000000000000000000000000000..eda10e6123e1e1383c4617228ec0c96680d60dc7 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@;M7UB8wRq zxI00Z(fs7;wLn1!PZ!4!jfu$#Gb-B{xqtMx1s*(O%+nLV7IxRdaaX6Iu&3070|zpW ye6d-ZyJWwc+pgOl@x443T>9p%`1p8cx&%YrImy|zOvgZGF?hQAxvX literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png new file mode 100644 index 0000000000000000000000000000000000000000..88726b69160589c8545759440e8d4e69dc984c67 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^azGr$!3HF6SgS1tQk(@Ik;M!Q+?^oIXnykaTA*No zr;B4qM&sKXhJ1$tcw7#es=J@sd2yMiyW@jyg@u( + + + + + + diff --git a/src/main/res/drawable/md__list_selector_holo_dark.xml b/src/main/res/drawable/md__list_selector_holo_dark.xml new file mode 100644 index 00000000..bd71aa8b --- /dev/null +++ b/src/main/res/drawable/md__list_selector_holo_dark.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/src/main/res/layout/menu_row_category.xml b/src/main/res/layout/menu_row_category.xml new file mode 100644 index 00000000..c89e5d96 --- /dev/null +++ b/src/main/res/layout/menu_row_category.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/src/main/res/layout/menu_row_item.xml b/src/main/res/layout/menu_row_item.xml new file mode 100644 index 00000000..af2c4b38 --- /dev/null +++ b/src/main/res/layout/menu_row_item.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index bc48ae9a..c9f62e57 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -22,5 +22,7 @@ #646464 #c8c8c8 #323232 + #00000000 + #FF555555 \ No newline at end of file diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml new file mode 100644 index 00000000..925ac058 --- /dev/null +++ b/src/main/res/values/ids.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index 93e381d0..a2272424 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -29,5 +29,36 @@ @style/Widget.Holo.Light.CompoundButton.Switch + + + + \ No newline at end of file diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 8760a48c..47d83b39 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -46,7 +46,7 @@ import android.os._ import android.preference.{Preference, PreferenceManager} import android.util.Log import android.view.{ViewGroup, Gravity, ViewParent, KeyEvent} -import android.widget.{LinearLayout, CompoundButton, RelativeLayout, TextView} +import android.widget._ import com.actionbarsherlock.view.Menu import com.actionbarsherlock.view.MenuItem import com.google.analytics.tracking.android.EasyTracker @@ -68,7 +68,11 @@ import scala.concurrent.ops._ import android.content.SharedPreferences.OnSharedPreferenceChangeListener import com.google.ads.{AdRequest, AdSize, AdView} import net.simonvt.menudrawer.MenuDrawer -import javax.swing.text.html.ListView + +import com.github.shadowsocks.database.{MenuAdapter, Item, Category} +import scala.collection.mutable.ListBuffer +import com.github.shadowsocks.database.Category +import com.github.shadowsocks.database.Item object Shadowsocks { @@ -222,6 +226,8 @@ class Shadowsocks lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE) lazy val receiver = new StateBroadcastReceiver lazy val drawer = MenuDrawer.attach(this) + lazy val listView = new ListView(this) + lazy val menuAdapter = new MenuAdapter(this, getMenuList) private val handler: Handler = new Handler { override def handleMessage(msg: Message) { @@ -399,11 +405,8 @@ class Shadowsocks setHeaderRes(R.xml.shadowsocks_headers) super.onCreate(savedInstanceState) - val menuView = new TextView(this) - menuView.setTextColor(0xFFFFFFFF) - menuView.setText("As the drawer opens, the drawer indicator icon becomes smaller.") - menuView.setGravity(Gravity.CENTER) - drawer.setMenuView(menuView) + listView.setAdapter(menuAdapter) + drawer.setMenuView(listView) // The drawable that replaces the up indicator in the action bar drawer.setSlideDrawable(R.drawable.ic_drawer) @@ -440,26 +443,44 @@ class Shadowsocks handler.sendEmptyMessage(MSG_INITIAL_FINISH) } } + } -/* override def onCreateOptionsMenu(menu: Menu): Boolean = { - val isRoot = status.getBoolean(Key.isRoot, false) - menu - .add(0, 0, 0, R.string.recovery) - .setIcon(android.R.drawable.ic_menu_revert) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) - menu - .add(0, 1, 1, R.string.about) - .setIcon(android.R.drawable.ic_menu_info_details) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) - if (isRoot || Build.VERSION.SDK_INT < 17) { - menu - .add(0, 2, 2, R.string.flush_dnscache) - .setIcon(android.R.drawable.ic_menu_revert) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) - } - true - }*/ + def getProfileList: List[Item] = { + List[Item]() + } + + def getMenuList: List[Any] = { + + val buf = new ListBuffer[Any]() + + buf += new Category("Profiles") + + buf ++= getProfileList + + buf += new Category("Settings") + + buf += new Item(-1, getString(R.string.recovery), android.R.drawable.ic_menu_revert, + _ => { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) + recovery() + }) + + buf += new Item(-2, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, + _ => { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L) + flushDnsCache() + }) + + buf += new Item(-3, getString(R.string.about), android.R.drawable.ic_menu_info_details, + _ => { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L) + showAbout() + }) + + buf.toList + + } override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) { @@ -481,18 +502,6 @@ class Shadowsocks drawer.toggleMenu() return true } - case 0 => { - EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) - recovery() - } - case 1 => { - EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L) - showAbout() - } - case 2 => { - EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L) - flushDnsCache() - } } super.onOptionsItemSelected(item) } diff --git a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala new file mode 100644 index 00000000..1554d6a9 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala @@ -0,0 +1,113 @@ +package com.github.shadowsocks.database + +import android.content.{DialogInterface, Context} +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.github.shadowsocks.R +import android.view.View.OnClickListener + +case class Item(id: Int, title: String, iconRes: Int, action: Int => Unit) + +case class Category (title: String) + +object MenuAdapter { + + trait MenuListener { + def onActiveViewChanged(v: View) + } + + val UNDEFINED = -1 + val CATEGORY = 0 + val ITEM = 1 + +} + +class MenuAdapter (context: Context, var items: List[Any]) extends BaseAdapter { + + def setListener(listener: MenuAdapter.MenuListener) { + mListener = listener + } + + def setActivePosition(activePosition: Int) { + mActivePosition = activePosition + } + + @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 Item => 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) + tv.setCompoundDrawablesWithIntrinsicBounds(value.iconRes, 0, 0, 0) + tv.setOnClickListener(new OnClickListener { + def onClick(view: View) { + val item = view.getTag(R.id.mdItem).asInstanceOf[Item] + item.action(item.id) + } + }) + + v.setTag(R.id.mdItem, value) + case _ => + } + v.setTag(R.id.mdActiveViewPosition, position) + + if (position == mActivePosition) { + if (mListener != null) mListener.onActiveViewChanged(v) + } + v + } + + def updateList(list: List[Any]) { + items = list + notifyDataSetChanged() + } + + private var mListener: MenuAdapter.MenuListener = null + private var mActivePosition: Int = 0 +} + + From 6f55861df526713452e93c461e6c3ae6b9794c69 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 28 Sep 2013 20:14:00 +0800 Subject: [PATCH 3/6] model ready --- .../com/github/shadowsocks/Shadowsocks.scala | 11 ++- .../shadowsocks/ShadowsocksApplication.scala | 5 + .../shadowsocks/database/DBHelper.scala | 41 ++++++++- .../shadowsocks/database/MenuAdapter.scala | 39 ++++++++ .../github/shadowsocks/database/Profile.scala | 41 ++++++++- .../shadowsocks/database/ProfileManager.scala | 91 +++++++++++++++++++ 6 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/com/github/shadowsocks/database/ProfileManager.scala diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 47d83b39..509804b8 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -69,10 +69,11 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener import com.google.ads.{AdRequest, AdSize, AdView} import net.simonvt.menudrawer.MenuDrawer -import com.github.shadowsocks.database.{MenuAdapter, Item, Category} +import com.github.shadowsocks.database._ import scala.collection.mutable.ListBuffer import com.github.shadowsocks.database.Category import com.github.shadowsocks.database.Item +import com.github.shadowsocks.database.Profile object Shadowsocks { @@ -228,6 +229,7 @@ class Shadowsocks lazy val drawer = MenuDrawer.attach(this) lazy val listView = new ListView(this) lazy val menuAdapter = new MenuAdapter(this, getMenuList) + lazy val profileManager = getApplication.asInstanceOf[ShadowsocksApplication].profileManager private val handler: Handler = new Handler { override def handleMessage(msg: Message) { @@ -446,8 +448,13 @@ class Shadowsocks } + def updateProfile(id: Int) { + + } + def getProfileList: List[Item] = { - List[Item]() + val list = profileManager.getAllProfiles getOrElse List[Profile]() + list.map(p => new Item(p.id, p.name, -1, updateProfile)) } def getMenuList: List[Any] = { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index f704c868..51548324 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -40,9 +40,14 @@ package com.github.shadowsocks import android.app.Application import com.google.analytics.tracking.android.EasyTracker +import com.github.shadowsocks.database.{ProfileManager, DBHelper} class ShadowsocksApplication extends Application { + lazy val dbHelper = new DBHelper(this) + lazy val profileManager = new ProfileManager(dbHelper) + override def onCreate() { EasyTracker.getInstance.setContext(this) } + } diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 654c0acb..00b91314 100644 --- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -1,3 +1,42 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2013 + * + * 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 . + * + * + * ___====-_ _-====___ + * _--^^^#####// \\#####^^^--_ + * _-^##########// ( ) \\##########^-_ + * -############// |\^^/| \\############- + * _/############// (@::@) \\############\_ + * /#############(( \\// ))#############\ + * -###############\\ (oo) //###############- + * -#################\\ / VV \ //#################- + * -###################\\/ \//###################- + * _#/|##########/\######( /\ )######/\##########|\#_ + * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| + * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' + * ` ` ` ` / | | | | \ ' ' ' ' + * ( | | | | ) + * __\ | | | | /__ + * (vvv(VVV)(VVV)vvv) + * + * HERE BE DRAGONS + * + */ + package com.github.shadowsocks.database import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper @@ -7,7 +46,7 @@ import com.j256.ormlite.support.ConnectionSource import com.j256.ormlite.table.TableUtils import com.j256.ormlite.dao.Dao -class DBHelper(implicit val context: Context) extends OrmLiteSqliteOpenHelper(context, "postnauka", null, 3) { +class DBHelper(val context: Context) extends OrmLiteSqliteOpenHelper(context, "profile.db", null, 3) { lazy val profileDao:Dao[Profile,Int] = getDao(classOf[Profile]) diff --git a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala index 1554d6a9..096f3564 100644 --- a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala +++ b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala @@ -1,3 +1,42 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2013 + * + * 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 . + * + * + * ___====-_ _-====___ + * _--^^^#####// \\#####^^^--_ + * _-^##########// ( ) \\##########^-_ + * -############// |\^^/| \\############- + * _/############// (@::@) \\############\_ + * /#############(( \\// ))#############\ + * -###############\\ (oo) //###############- + * -#################\\ / VV \ //#################- + * -###################\\/ \//###################- + * _#/|##########/\######( /\ )######/\##########|\#_ + * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| + * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' + * ` ` ` ` / | | | | \ ' ' ' ' + * ( | | | | ) + * __\ | | | | /__ + * (vvv(VVV)(VVV)vvv) + * + * HERE BE DRAGONS + * + */ + package com.github.shadowsocks.database import android.content.{DialogInterface, Context} diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala index b034aab0..8b4aca78 100644 --- a/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -1,3 +1,42 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2013 + * + * 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 . + * + * + * ___====-_ _-====___ + * _--^^^#####// \\#####^^^--_ + * _-^##########// ( ) \\##########^-_ + * -############// |\^^/| \\############- + * _/############// (@::@) \\############\_ + * /#############(( \\// ))#############\ + * -###############\\ (oo) //###############- + * -#################\\ / VV \ //#################- + * -###################\\/ \//###################- + * _#/|##########/\######( /\ )######/\##########|\#_ + * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| + * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' + * ` ` ` ` / | | | | \ ' ' ' ' + * ( | | | | ) + * __\ | | | | /__ + * (vvv(VVV)(VVV)vvv) + * + * HERE BE DRAGONS + * + */ + package com.github.shadowsocks.database import com.j256.ormlite.field.{DataType, DatabaseField} @@ -9,7 +48,7 @@ class Profile { @DatabaseField var name: String = null - @DatabaseField(foreign = true, canBeNull = false) + @DatabaseField var host: String = null @DatabaseField diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala new file mode 100644 index 00000000..d456a268 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -0,0 +1,91 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2013 + * + * 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 . + * + * + * ___====-_ _-====___ + * _--^^^#####// \\#####^^^--_ + * _-^##########// ( ) \\##########^-_ + * -############// |\^^/| \\############- + * _/############// (@::@) \\############\_ + * /#############(( \\// ))#############\ + * -###############\\ (oo) //###############- + * -#################\\ / VV \ //#################- + * -###################\\/ \//###################- + * _#/|##########/\######( /\ )######/\##########|\#_ + * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| + * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' + * ` ` ` ` / | | | | \ ' ' ' ' + * ( | | | | ) + * __\ | | | | /__ + * (vvv(VVV)(VVV)vvv) + * + * HERE BE DRAGONS + * + */ + +package com.github.shadowsocks.database + +import android.util.Log +import com.github.shadowsocks.Shadowsocks + +class ProfileManager(dbHelper: DBHelper) { + def addProfile(profile: Profile): Boolean = { + try { + dbHelper.profileDao.createOrUpdate(profile) + } catch { + case ex: Exception => + Log.e(Shadowsocks.TAG, "addProfile", ex) + return false + } + true + } + + def getProfile(id: Int): Option[Profile] = { + try { + dbHelper.profileDao.queryForId(id) match { + case profile: Profile => Option(profile) + case _ => None + } + } catch { + case ex: Exception => + Log.e(Shadowsocks.TAG, "getProfile", ex) + return None + } + } + + def delProfile(id: Int): Boolean = { + try { + dbHelper.profileDao.deleteById(id) + } catch { + case ex: Exception => + Log.e(Shadowsocks.TAG, "delProfile", ex) + return false + } + true + } + + def getAllProfiles: Option[List[Profile]] = { + try { + import scala.collection.JavaConversions._ + Option(dbHelper.profileDao.queryForAll().toList) + } catch { + case ex: Exception => + Log.e(Shadowsocks.TAG, "getAllProfiles", ex) + None + } + } +} From a2848ce53842eeab5d41d0b84e980e35fc51e0bb Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 29 Sep 2013 15:35:49 +0800 Subject: [PATCH 4/6] update UI --- src/main/res/layout/layout_switch.xml | 3 +- src/main/res/values/strings.xml | 3 + src/main/res/xml/pref_proxy.xml | 6 + .../com/github/shadowsocks/AppManager.scala | 5 +- .../com/github/shadowsocks/Shadowsocks.scala | 408 ++++++++++++------ .../shadowsocks/ShadowsocksApplication.scala | 1 - .../shadowsocks/ShadowsocksService.scala | 14 +- .../scala/com/github/shadowsocks/Utils.scala | 49 ++- .../shadowsocks/database/DBHelper.scala | 2 +- .../shadowsocks/database/MenuAdapter.scala | 48 ++- .../github/shadowsocks/database/Profile.scala | 24 +- .../shadowsocks/database/ProfileManager.scala | 82 +++- 12 files changed, 450 insertions(+), 195 deletions(-) diff --git a/src/main/res/layout/layout_switch.xml b/src/main/res/layout/layout_switch.xml index 51ca4b72..3d0aabc6 100644 --- a/src/main/res/layout/layout_switch.xml +++ b/src/main/res/layout/layout_switch.xml @@ -9,7 +9,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" - android:textSize="22sp" + android:textSize="24sp" android:textColor="@color/title" android:text="@string/app_name" android:layout_alignParentLeft="true" @@ -20,6 +20,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" + android:layout_marginRight = "10dp" android:layout_centerVertical="true"/> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2838123b..dd1e3d8d 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -10,6 +10,8 @@ Server Settings + Profile + Profile name Server Hostname or IP Address of your Shadowsocks server Remote Port @@ -73,5 +75,6 @@ Shadowsocks %s Flush DNS Cache Flushing + Add Profile diff --git a/src/main/res/xml/pref_proxy.xml b/src/main/res/xml/pref_proxy.xml index 08f15dbc..5c9b61cf 100755 --- a/src/main/res/xml/pref_proxy.xml +++ b/src/main/res/xml/pref_proxy.xml @@ -1,6 +1,12 @@ + + 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) + case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password) + case Key.encMethod => updateListPreference(pref, profile.method) + case Key.isGFWList => updateCheckBoxPreference(pref, profile.chnroute) + case Key.isGlobalProxy => updateCheckBoxPreference(pref, profile.global) + case Key.isTrafficStat => updateCheckBoxPreference(pref, profile.traffic) + case _ => + } + } + + def isServiceStarted(context: Context): Boolean = { + ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context) + } + + class ProxyFragment extends UnifiedPreferenceFragment { + + var receiver: BroadcastReceiver = null + + private def setPreferenceEnabled() { + val state = getActivity.asInstanceOf[Shadowsocks].state + val enabled = state != State.CONNECTED && state != State.CONNECTING + for (name <- PROXY_PREFS) { + val pref: Preference = findPreference(name) + if (pref != null) { + pref.setEnabled(enabled) + } + } + } + + private def updatePreferenceScreen() { + for (name <- Shadowsocks.PROXY_PREFS) { + val pref = findPreference(name) + Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile) + } + } + + override def onCreate(bundle: Bundle) { + super.onCreate(bundle) + val filter = new IntentFilter() + filter.addAction(Action.UPDATE_FRAGMENT) + receiver = new BroadcastReceiver { + def onReceive(p1: Context, p2: Intent) { + setPreferenceEnabled() + updatePreferenceScreen() + } + } + getActivity.getApplicationContext.registerReceiver(receiver, filter) + } + + override def onDestroy() { + super.onDestroy() + getActivity.getApplicationContext.unregisterReceiver(receiver) + } + + override def onResume() { + super.onResume() + setPreferenceEnabled() + } + + override def onPause() { + super.onPause() + } + } + + class FeatureFragment extends UnifiedPreferenceFragment { + + var receiver: BroadcastReceiver = null + + private def setPreferenceEnabled() { + val state = getActivity.asInstanceOf[Shadowsocks].state + val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING + for (name <- Shadowsocks.FEATRUE_PREFS) { + val pref: Preference = findPreference(name) + if (pref != null) { + val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE) + val isRoot = status.getBoolean(Key.isRoot, false) + if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps) + .contains(name)) { + pref.setEnabled(enabled && isRoot) + } else { + pref.setEnabled(enabled) + } + } + } + } + + private def updatePreferenceScreen() { + for (name <- Shadowsocks.FEATRUE_PREFS) { + val pref = findPreference(name) + Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile) + } + } + + override def onCreate(bundle: Bundle) { + super.onCreate(bundle) + val filter = new IntentFilter() + filter.addAction(Action.UPDATE_FRAGMENT) + receiver = new BroadcastReceiver { + def onReceive(p1: Context, p2: Intent) { + setPreferenceEnabled() + updatePreferenceScreen() + } + } + getActivity.getApplicationContext.registerReceiver(receiver, filter) + } + + override def onDestroy() { + super.onDestroy() + getActivity.getApplicationContext.unregisterReceiver(receiver) + } + + override def onResume() { + super.onResume() + setPreferenceEnabled() + } + + override def onPause() { + super.onPause() + } + } + +} + class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.OnCheckedChangeListener - with OnSharedPreferenceChangeListener { + with OnSharedPreferenceChangeListener + with MenuAdapter.MenuListener{ private val MSG_CRASH_RECOVER: Int = 1 private val MSG_INITIAL_FINISH: Int = 2 @@ -227,9 +299,9 @@ class Shadowsocks lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE) lazy val receiver = new StateBroadcastReceiver lazy val drawer = MenuDrawer.attach(this) - lazy val listView = new ListView(this) lazy val menuAdapter = new MenuAdapter(this, getMenuList) - lazy val profileManager = getApplication.asInstanceOf[ShadowsocksApplication].profileManager + lazy val listView = new ListView(this) + lazy val profileManager = new ProfileManager(settings, getApplication.asInstanceOf[ShadowsocksApplication].dbHelper) private val handler: Handler = new Handler { override def handleMessage(msg: Message) { @@ -291,10 +363,10 @@ class Shadowsocks } } - def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { - if (key == Key.isGlobalProxy) { - val enabled = state != State.CONNECTED && state != State.CONNECTING - setPreferenceEnabled(enabled) + override def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { + if (key == Key.profileName) { + profileManager.save() + menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id) } } @@ -407,6 +479,10 @@ class Shadowsocks setHeaderRes(R.xml.shadowsocks_headers) super.onCreate(savedInstanceState) + Shadowsocks.currentProfile = { + profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse new Profile() + } + listView.setAdapter(menuAdapter) drawer.setMenuView(listView) @@ -422,7 +498,6 @@ class Shadowsocks getSupportActionBar.setDisplayShowTitleEnabled(false) getSupportActionBar.setDisplayShowCustomEnabled(true) // getSupportActionBar.setDisplayShowHomeEnabled(false) - val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView] val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf") if (tf != null) title.setTypeface(tf) @@ -445,16 +520,49 @@ class Shadowsocks handler.sendEmptyMessage(MSG_INITIAL_FINISH) } } + } + override def onActiveViewChanged(v: View, pos: Int) { + drawer.setActiveView(v, pos) } def updateProfile(id: Int) { + drawer.closeMenu(true) + handler.postDelayed(new Runnable { + def run() { + Shadowsocks.currentProfile = profileManager.load(id) + updatePreferenceScreen() + } + }, 600) + + if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT)) + } + + def delProfile(id: Int): Boolean = { + drawer.closeMenu(true) + + handler.postDelayed(new Runnable { + def run() { + profileManager.delProfile(id) + val profileId = { + val profiles = profileManager.getAllProfiles.getOrElse(List[Profile]()) + if (profiles.isEmpty) -1 else profiles(0).id + } + Shadowsocks.currentProfile = profileManager.load(profileId) + updatePreferenceScreen() + menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id) + } + }, 600) + + if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT)) + + true } def getProfileList: List[Item] = { val list = profileManager.getAllProfiles getOrElse List[Profile]() - list.map(p => new Item(p.id, p.name, -1, updateProfile)) + list.map(p => new Item(p.id, p.name, -1, updateProfile, delProfile)) } def getMenuList: List[Any] = { @@ -465,28 +573,31 @@ class Shadowsocks buf ++= getProfileList + buf += new Item(-1, getString(R.string.add_profile), android.R.drawable.ic_menu_add, _ => { + updateProfile(-1) + profileManager.save() + menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id) + }) + buf += new Category("Settings") - buf += new Item(-1, getString(R.string.recovery), android.R.drawable.ic_menu_revert, - _ => { - EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) - recovery() - }) + buf += new Item(-100, getString(R.string.recovery), android.R.drawable.ic_menu_revert, _ => { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) + recovery() + }) - buf += new Item(-2, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, - _ => { + buf += + new Item(-200, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, _ => { EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L) flushDnsCache() }) - buf += new Item(-3, getString(R.string.about), android.R.drawable.ic_menu_info_details, - _ => { - EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L) - showAbout() - }) + buf += new Item(-300, getString(R.string.about), android.R.drawable.ic_menu_info_details, _ => { + EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L) + showAbout() + }) buf.toList - } override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = { @@ -516,6 +627,7 @@ class Shadowsocks protected override def onPause() { super.onPause() prepared = false + settings.unregisterOnSharedPreferenceChangeListener(this) } protected override def onResume() { @@ -549,6 +661,8 @@ class Shadowsocks } switchButton.setOnCheckedChangeListener(this) + + settings.registerOnSharedPreferenceChangeListener(this) } private def setPreferenceEnabled(enabled: Boolean) { @@ -562,8 +676,8 @@ class Shadowsocks for (name <- Shadowsocks.FEATRUE_PREFS) { val pref = findPreference(name) if (pref != null) { - if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, - Key.proxyedApps).contains(name)) { + if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps) + .contains(name)) { pref.setEnabled(enabled && isRoot) } else { pref.setEnabled(enabled) @@ -572,6 +686,18 @@ class Shadowsocks } } + private def updatePreferenceScreen() { + val profile = Shadowsocks.currentProfile + for (name <- Shadowsocks.PROXY_PREFS) { + val pref = findPreference(name) + Shadowsocks.updatePreference(pref, name, profile) + } + for (name <- Shadowsocks.FEATRUE_PREFS) { + val pref = findPreference(name) + Shadowsocks.updatePreference(pref, name, profile) + } + } + override def onStart() { super.onStart() EasyTracker.getInstance.activityStart(this) @@ -790,7 +916,7 @@ class Shadowsocks setPreferenceEnabled(enabled = true) } } - sendBroadcast(new Intent(Action.UPDATE_FRAGMENT)) + if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT)) } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 51548324..9a3703ab 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -44,7 +44,6 @@ import com.github.shadowsocks.database.{ProfileManager, DBHelper} class ShadowsocksApplication extends Application { lazy val dbHelper = new DBHelper(this) - lazy val profileManager = new ProfileManager(dbHelper) override def onCreate() { EasyTracker.getInstance.setContext(this) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala index 242e5ff0..2dd7b27a 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala @@ -324,18 +324,8 @@ class ShadowsocksService extends Service { val icon = getResources.getDrawable(R.drawable.ic_stat_shadowsocks) if (rate >= 0) { - val bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth * 4, - icon.getIntrinsicHeight * 4, Bitmap.Config.ARGB_8888) - val r = rate.toString - val size = bitmap.getHeight / 4 - val canvas = new Canvas(bitmap) - val paint = new Paint() - paint.setColor(Color.WHITE) - paint.setTextSize(size) - val bounds = new Rect() - paint.getTextBounds(r, 0, r.length, bounds) - canvas.drawText(r, (bitmap.getWidth - bounds.width()) / 2, - bitmap.getHeight - (bitmap.getHeight - bounds.height()) / 2, paint) + val bitmap =Utils.getBitmap(rate.toString, icon.getIntrinsicWidth * 4, + icon.getIntrinsicHeight * 4, Color.TRANSPARENT) builder.setLargeIcon(bitmap) if (rate < 1000) { diff --git a/src/main/scala/com/github/shadowsocks/Utils.scala b/src/main/scala/com/github/shadowsocks/Utils.scala index dc9dc6a3..e3e63e9c 100644 --- a/src/main/scala/com/github/shadowsocks/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/Utils.scala @@ -49,10 +49,11 @@ import org.apache.http.conn.util.InetAddressUtils import scala.collection.mutable.ArrayBuffer import org.xbill.DNS._ import scala.Some -import android.graphics.{Canvas, Bitmap} +import android.graphics._ import android.app.ActivityManager import android.os.Build import android.provider.Settings +import scala.Some object Config { val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}" @@ -112,10 +113,13 @@ object Config { } case class Config(isGlobalProxy: Boolean, isGFWList: Boolean, isBypassApps: Boolean, isTrafficStat: Boolean, - var proxy: String, sitekey: String, encMethod: String, remotePort: Int, + profileName: String, var proxy: String, sitekey: String, encMethod: String, remotePort: Int, localPort: Int, proxiedAppString: String) object Key { + val profileId = "profileId" + val profileName = "profileName" + val proxied = "Proxyed" val isRoot = "isRoot" @@ -137,6 +141,11 @@ object Key { val localPort = "port" } +object Scheme { + val APP = "app://" + val PROFILE = "profile://" +} + object State { val INIT = 0 val CONNECTING = 1 @@ -162,6 +171,7 @@ object Extra { edit.putBoolean(Key.isBypassApps, config.isBypassApps) edit.putBoolean(Key.isTrafficStat, config.isTrafficStat) + edit.putString(Key.profileName, config.profileName) edit.putString(Key.proxy, config.proxy) edit.putString(Key.sitekey, config.sitekey) edit.putString(Key.encMethod, config.encMethod) @@ -177,6 +187,7 @@ object Extra { val isBypassApps = intent.getBooleanExtra(Key.isBypassApps, false) val isTrafficStat = intent.getBooleanExtra(Key.isTrafficStat, false) + val profileName = intent.getStringExtra(Key.profileName) val proxy = intent.getStringExtra(Key.proxy) val sitekey = intent.getStringExtra(Key.sitekey) val encMethod = intent.getStringExtra(Key.encMethod) @@ -184,7 +195,7 @@ object Extra { val localPort = intent.getIntExtra(Key.localPort, 1984) val proxiedString = intent.getStringExtra(Key.proxied) - new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, proxy, sitekey, encMethod, remotePort, + new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, profileName, proxy, sitekey, encMethod, remotePort, localPort, proxiedString) } @@ -194,6 +205,7 @@ object Extra { val isBypassApps = settings.getBoolean(Key.isBypassApps, false) val isTrafficStat = settings.getBoolean(Key.isTrafficStat, false) + val profileName = settings.getString(Key.profileName, "default") val proxy = settings.getString(Key.proxy, "127.0.0.1") match { case "198.199.101.152" => BuildConfig.SERVER case s: String => s @@ -232,6 +244,7 @@ object Extra { intent.putExtra(Key.isBypassApps, isBypassApps) intent.putExtra(Key.isTrafficStat, isTrafficStat) + intent.putExtra(Key.profileName, profileName) intent.putExtra(Key.proxy, proxy) intent.putExtra(Key.sitekey, sitekey) intent.putExtra(Key.encMethod, encMethod) @@ -265,6 +278,36 @@ object Utils { var data_path: String = null var rootTries = 0 + /* + * round or floor depending on whether you are using offsets(floor) or + * widths(round) + */ + def dpToPx (context: Context, dp: Float): Float = { + val density = context.getResources.getDisplayMetrics.density + dp * density + } + + def pxToDp (context: Context, px: Float): Float = { + val density = context.getResources.getDisplayMetrics.density + px / density + } + + def getBitmap(text: String, width: Int, height: Int, background: Int): Bitmap = { + val bitmap = Bitmap.createBitmap(width, + height, Bitmap.Config.ARGB_8888) + val size = bitmap.getHeight / 4 + val canvas = new Canvas(bitmap) + canvas.drawColor(background) + val paint = new Paint() + paint.setColor(Color.WHITE) + paint.setTextSize(size) + val bounds = new Rect() + paint.getTextBounds(text, 0, text.length, bounds) + canvas.drawText(text, (bitmap.getWidth - bounds.width()) / 2, + bitmap.getHeight - (bitmap.getHeight - bounds.height()) / 2, paint) + bitmap + } + // Blocked > 3 seconds def toggleAirplaneMode(context: Context) { if (Build.VERSION.SDK_INT >= 17) { diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 00b91314..9ed987e4 100644 --- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -46,7 +46,7 @@ import com.j256.ormlite.support.ConnectionSource import com.j256.ormlite.table.TableUtils import com.j256.ormlite.dao.Dao -class DBHelper(val context: Context) extends OrmLiteSqliteOpenHelper(context, "profile.db", null, 3) { +class DBHelper(val context: Context) extends OrmLiteSqliteOpenHelper(context, "profile.db", null, 7) { lazy val profileDao:Dao[Profile,Int] = getDao(classOf[Profile]) diff --git a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala index 096f3564..ef2d1077 100644 --- a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala +++ b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala @@ -39,39 +39,39 @@ package com.github.shadowsocks.database -import android.content.{DialogInterface, Context} +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView -import com.github.shadowsocks.R -import android.view.View.OnClickListener +import com.github.shadowsocks.{Shadowsocks, R} +import android.view.View.{OnLongClickListener, OnClickListener} -case class Item(id: Int, title: String, iconRes: Int, action: Int => Unit) +case class Item(id: Int, title: String, iconRes: Int, + click: Int => Unit, longClick: Int => Boolean = _ => false) -case class Category (title: String) +case class Category(title: String) object MenuAdapter { trait MenuListener { - def onActiveViewChanged(v: View) + 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 { +class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter { def setListener(listener: MenuAdapter.MenuListener) { - mListener = listener + this.listener = listener } def setActivePosition(activePosition: Int) { - mActivePosition = activePosition + this.activePosition = activePosition } @Override def getCount: Int = { @@ -121,32 +121,46 @@ class MenuAdapter (context: Context, var items: List[Any]) extends BaseAdapter { } val tv: TextView = v.asInstanceOf[TextView] tv.setText(value.title) - tv.setCompoundDrawablesWithIntrinsicBounds(value.iconRes, 0, 0, 0) + if (value.iconRes != -1) { + tv.setCompoundDrawablesWithIntrinsicBounds(value.iconRes, 0, 0, 0) + } tv.setOnClickListener(new OnClickListener { def onClick(view: View) { val item = view.getTag(R.id.mdItem).asInstanceOf[Item] - item.action(item.id) + item.click(item.id) + } + }) + tv.setOnLongClickListener(new OnLongClickListener { + def onLongClick(view: View): Boolean = { + val item = view.getTag(R.id.mdItem).asInstanceOf[Item] + item.longClick(item.id) } }) v.setTag(R.id.mdItem, value) + case _ => } v.setTag(R.id.mdActiveViewPosition, position) - if (position == mActivePosition) { - if (mListener != null) mListener.onActiveViewChanged(v) + if (position == activePosition) { + if (listener != null) listener.onActiveViewChanged(v, position) } + v } - def updateList(list: List[Any]) { + def updateList(list: List[Any], activeId: Int) { items = list + activePosition = items.indexWhere{ + case item: Item => item.id == activeId + case _ => false + } notifyDataSetChanged() } - private var mListener: MenuAdapter.MenuListener = null - private var mActivePosition: Int = 0 + private var listener: MenuAdapter.MenuListener = null + private var activePosition: Int = -1 } diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala index 8b4aca78..38f7d711 100644 --- a/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -42,14 +42,14 @@ package com.github.shadowsocks.database import com.j256.ormlite.field.{DataType, DatabaseField} class Profile { - @DatabaseField(id = true) + @DatabaseField(generatedId = true) var id: Int = 0 @DatabaseField - var name: String = null + var name: String = "Untitled" @DatabaseField - var host: String = null + var host: String = "" @DatabaseField var localPort: Int = 0 @@ -58,13 +58,13 @@ class Profile { var remotePort: Int = 0 @DatabaseField - var password: String = null + var password: String = "" @DatabaseField - var method: String = null + var method: String = "" @DatabaseField - var date: String = null + var date: String = "" @DatabaseField var upload: Int = 0 @@ -78,9 +78,15 @@ class Profile { @DatabaseField var global: Boolean = false - @DatabaseField(dataType = DataType.LONG_STRING) - var individual: String = null + @DatabaseField + var bypass: Boolean = false + + @DatabaseField + var traffic: Boolean = false @DatabaseField(dataType = DataType.LONG_STRING) - var description: String = null + var individual: String = "" + + @DatabaseField(dataType = DataType.LONG_STRING) + var description: String = "" } diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index d456a268..4b0b667d 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -40,18 +40,20 @@ package com.github.shadowsocks.database import android.util.Log -import com.github.shadowsocks.Shadowsocks +import com.github.shadowsocks._ +import android.content.{SharedPreferences, Context} -class ProfileManager(dbHelper: DBHelper) { - def addProfile(profile: Profile): Boolean = { +class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { + + def createOrUpdateProfile(profile: Profile): Boolean = { try { dbHelper.profileDao.createOrUpdate(profile) + true } catch { case ex: Exception => Log.e(Shadowsocks.TAG, "addProfile", ex) - return false + false } - true } def getProfile(id: Int): Option[Profile] = { @@ -70,12 +72,12 @@ class ProfileManager(dbHelper: DBHelper) { def delProfile(id: Int): Boolean = { try { dbHelper.profileDao.deleteById(id) + true } catch { case ex: Exception => Log.e(Shadowsocks.TAG, "delProfile", ex) - return false + false } - true } def getAllProfiles: Option[List[Profile]] = { @@ -88,4 +90,70 @@ class ProfileManager(dbHelper: DBHelper) { None } } + + def load(id: Int): Profile = { + + Log.d(Shadowsocks.TAG, id + " ") + + save() + + val profile = getProfile(id) getOrElse { + val p = new Profile() + createOrUpdateProfile(p) + p + } + + Log.d(Shadowsocks.TAG, "load " + profile.id + " " + profile.name) + + val edit = settings.edit() + edit.putBoolean(Key.isGlobalProxy, profile.global) + edit.putBoolean(Key.isGFWList, profile.chnroute) + edit.putBoolean(Key.isBypassApps, profile.bypass) + edit.putBoolean(Key.isTrafficStat, profile.traffic) + edit.putString(Key.profileName, profile.name) + edit.putString(Key.proxy, profile.host) + edit.putString(Key.sitekey, profile.password) + edit.putString(Key.encMethod, profile.method) + edit.putString(Key.remotePort, profile.remotePort.toString) + edit.putString(Key.localPort, profile.localPort.toString) + edit.putString(Key.proxied, profile.individual) + edit.putInt(Key.profileId, profile.id) + edit.commit() + + profile + } + + def save() { + val profile = new Profile() + + profile.global = settings.getBoolean(Key.isGlobalProxy, false) + profile.chnroute = settings.getBoolean(Key.isGFWList, false) + profile.bypass = settings.getBoolean(Key.isBypassApps, false) + profile.traffic = settings.getBoolean(Key.isTrafficStat, false) + profile.name = settings.getString(Key.profileName, "default") + profile.host = settings.getString(Key.proxy, "127.0.0.1") + profile.password = settings.getString(Key.sitekey, "default") + profile.method = settings.getString(Key.encMethod, "table") + profile.remotePort = try { + Integer.valueOf(settings.getString(Key.remotePort, "1984")) + } catch { + case ex: NumberFormatException => { + 1984 + } + } + profile.localPort = try { + Integer.valueOf(settings.getString(Key.localPort, "1984")) + } catch { + case ex: NumberFormatException => { + 1984 + } + } + profile.individual = settings.getString(Key.proxied, "") + + profile.id = settings.getInt(Key.profileId, -1) + + Log.d(Shadowsocks.TAG, "save " + profile.id + " " + profile.name) + + createOrUpdateProfile(profile) + } } From 58dc3e5061cce658c67eaec210518b5e2ba4fc9a Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 29 Sep 2013 16:26:19 +0800 Subject: [PATCH 5/6] refine UI --- src/main/res/values/styles.xml | 5 ++ .../com/github/shadowsocks/Shadowsocks.scala | 49 ++++++++++++++++--- .../shadowsocks/database/MenuAdapter.scala | 18 +++---- .../shadowsocks/database/ProfileManager.scala | 11 ++--- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index a2272424..3382923c 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -23,6 +23,7 @@ + \ No newline at end of file diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 8f698033..7a1a2dde 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -289,6 +289,9 @@ class Shadowsocks private val MSG_CRASH_RECOVER: Int = 1 private val MSG_INITIAL_FINISH: Int = 2 + private val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer" + private val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId" + private var switchButton: Switch = null private var progressDialog: ProgressDialog = null @@ -483,6 +486,8 @@ class Shadowsocks profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse new Profile() } + menuAdapter.setActiveId(settings.getInt(Key.profileId, -1)) + menuAdapter.setListener(this) listView.setAdapter(menuAdapter) drawer.setMenuView(listView) @@ -522,17 +527,53 @@ 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, Shadowsocks.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 addProfile(id: Int) { + drawer.closeMenu(true) + + handler.postDelayed(new Runnable { + def run() { + Shadowsocks.currentProfile = profileManager.reload(id) + updatePreferenceScreen() + profileManager.save() + menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id) + } + }, 600) + + if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT)) + } + def updateProfile(id: Int) { drawer.closeMenu(true) handler.postDelayed(new Runnable { def run() { - Shadowsocks.currentProfile = profileManager.load(id) + Shadowsocks.currentProfile = profileManager.reload(id) updatePreferenceScreen() + menuAdapter.setActiveId(id) } }, 600) @@ -573,11 +614,7 @@ class Shadowsocks buf ++= getProfileList - buf += new Item(-1, getString(R.string.add_profile), android.R.drawable.ic_menu_add, _ => { - updateProfile(-1) - profileManager.save() - menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id) - }) + buf += new Item(-1, getString(R.string.add_profile), android.R.drawable.ic_menu_add, addProfile) buf += new Category("Settings") diff --git a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala index ef2d1077..66448a6e 100644 --- a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala +++ b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala @@ -70,8 +70,8 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter { this.listener = listener } - def setActivePosition(activePosition: Int) { - this.activePosition = activePosition + def setActiveId(activeId: Int) { + this.activeId = activeId } @Override def getCount: Int = { @@ -139,28 +139,26 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter { v.setTag(R.id.mdItem, value) + if (value.id == activeId) { + if (listener != null) listener.onActiveViewChanged(v, position) + } + case _ => } v.setTag(R.id.mdActiveViewPosition, position) - if (position == activePosition) { - if (listener != null) listener.onActiveViewChanged(v, position) - } v } def updateList(list: List[Any], activeId: Int) { items = list - activePosition = items.indexWhere{ - case item: Item => item.id == activeId - case _ => false - } + this.activeId = activeId notifyDataSetChanged() } private var listener: MenuAdapter.MenuListener = null - private var activePosition: Int = -1 + private var activeId: Int = -1 } diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 4b0b667d..d4772cdb 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -91,11 +91,12 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { } } - def load(id: Int): Profile = { - - Log.d(Shadowsocks.TAG, id + " ") - + def reload(id: Int): Profile = { save() + load(id) + } + + def load(id: Int): Profile = { val profile = getProfile(id) getOrElse { val p = new Profile() @@ -103,8 +104,6 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { p } - Log.d(Shadowsocks.TAG, "load " + profile.id + " " + profile.name) - val edit = settings.edit() edit.putBoolean(Key.isGlobalProxy, profile.global) edit.putBoolean(Key.isGFWList, profile.chnroute) From 1155c7317a80dfcfb7b6de63d3f1c88bf3228756 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 29 Sep 2013 17:27:45 +0800 Subject: [PATCH 6/6] fix #37 --- src/main/res/drawable-hdpi/menu_arrow.png | Bin 0 -> 1578 bytes src/main/res/drawable-mdpi/menu_arrow.png | Bin 0 -> 869 bytes src/main/res/drawable-xhdpi/menu_arrow.png | Bin 0 -> 1723 bytes .../com/github/shadowsocks/Shadowsocks.scala | 14 +------------- 4 files changed, 1 insertion(+), 13 deletions(-) create mode 100644 src/main/res/drawable-hdpi/menu_arrow.png create mode 100644 src/main/res/drawable-mdpi/menu_arrow.png create mode 100644 src/main/res/drawable-xhdpi/menu_arrow.png diff --git a/src/main/res/drawable-hdpi/menu_arrow.png b/src/main/res/drawable-hdpi/menu_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..c28d98f3bb435334b7852718214161d780896fce GIT binary patch literal 1578 zcmV+_2G#kAP)Zxv z2M=z{hJRsGLp+$k$#~wFcp=e)k~Hzkfg4K9#u!g}*mxi@9Pmew6;Q+~7U`7f%=_$t z)-4K!w!6tr@}A~>zu)(HKJ&iwyf2s;^)>(mzy&}Z9v)T!C;$WiqGE@dA0i?Eu1F-J z=5jgJ=ktlJtu4E)t<9RBpSP8NL%hAcy}`09ud1pZ$8kv4b`F-_AgglJ*rD1aUS&CL9HdU|@qFpQJH4(+OEKRi6_%49MI5j6wo zDvI|2@QjU(H4Y9A{_?edH7P1VZE0yS6h#RDhzTJE01N_%pEz;CGd(>$tZABBwLuxw z1|dWfGj}m_AAsXT)aUp6Lk}K2h=oESy?mTF^z;FW#bT=CI9@4b8#BiNBmpFdsAF<+ z@;mWE8xWD`@9$S^+jbLC3xE>T9sr$Ju3YInefsow;)gOQ<=r$*13)tob(V=o&!0aZ zzI5r5d*AVu7?i|YTU&KiRhy-h@gmgY0Q!>2W#&d9L`VqH z4IoKG-HnZn;dDCPQc1j`0g6zil%9r$hIT1s96M=AFmuO^8#jX8-Q5SKc+U>sLM>aV zJpek##>T?u&Ye^D8hfBYIaDHQDndPmoli1$_Uze?@$vC}p?;@V4*6TC%p4`664d^9 zJRZMy?_Rs#@2{BmJtNoyYOn+qK(}ES9qDwsts3!N1}N^3Qc91m>#fWjW9B3g9Rm=a zn3xFl_V!k@^8Ya?LA5N)ErbY2DWd>-h^PlZ=jiBY_`-z?TD9T;AWEsRZM%Vp`~V`w zStpq}dg|1v@YSnVJ++7fJC%TlL?W7$(r4RtSO}4nQVsw}hQr~msi~mKsAw+F~2;SL@q@pO> z%)9|$nVDYzSRkUM$B!ScT)TE{tJVkv05G$hpP#oiP0L9sGeU?Z0MD8EIe<5llanhm zGc#5#248p6^0LITtRuRvcQSJyGam;qU>L^HyLaz)R=Xd4?OaOL>guXRM4M8|cT&n% z02TneGEH+el}f$e*x0BF`mWg&O`P@h^=)0(KP!r|3gBhYWN~qE@$Kcymp7!8wHcI~ z7=~do^CxD01K>r`0*tcvUA`y$lw0u6_=yJKj%=`m@9{~&j=)QC3 zPV6xMcMxAuiz&G|sYVlmA$ zO`pr<3IpiH4)j4GL~LqmD!NyN{r@?Dlta(w^I6Anc0vF2>C?*S=;)kfS%sPm%1oSS zG@93SeM3={6)ELQ?C@B8`t<4I&6_v>*cEw==VXycBvjKhy^iAqg%Al8kFfwg-@kwV z=fT0jU+XbA^g+C)-&Bdm^768!D9Yyc_V$_(;~rg-L{lp zhA^|2h&-C65dhQec5gm;@}$46ilGF3ZEbB^)3j`%Q25|-xia~DKC39ob|4V=v+m2N cgD#~02K77}GG35EQvd(}07*qoM6N<$f;$N{`01*y{8%w29 zLGZvPKCcc83^;Ni0U(#lSzfQVB&9R~G^A3g zMorUNVzF4=aTomODw;l@FAv}wfFv_-O-xLD-`?J~>H+|OnR$12_l$^+gb-htdF|}% zY-eO-2$hudV2cPw(S&vwS$9$)adAFrk2zz)5y$rHk&PaJf5^^ znp*&p$z<~L^z?MW5#apT`}_OH0QQJz1Hi`o{QTEMB2jV#xUJOxvlA~WsUr2$*x1;f zX_|Ijz&Yfa)YaA1op?OHTDOJDRsbl$V6d5pqC$usB2rdXR*Xm_^3KuYdaFFb?{!3Y}R7t9wIuTwN5mHj{%4? zGnm=V<#Ipn1`kBQE_CDL={Bk#Iw3@l*1DgWhlnVRot%#WNCOy} znVCsmzI-{_)?YUa_@J}u^?FaORvTdEB!Dp@IzdDynfW+?k*TSvR4$i0YTI^OpB;}t z>zmH=yr>W&CZ$XfQ5L|MF(wNjl}@LR-oJl;@N>a|4jJeKIsjQBN-3oV=jZ2#5{U$L z;=3CN+d_w(yvJ|fzMYtyoRl3q{oD=RKsP--ow#!4%D&U>K>zI^>=L@+TrSs9-u-sq z2pj^sShvA}0~iPsx{d@iEfnbX1Zn*tZ0PpH^F#P0plh{HBk-l5Yc;DSxX5HOw$?f{ z=vvLsfXHUEHWBsOwtWOZf`~HM0WL7;_6YE8B&20ojxnYWKztXvW@!}|bgd@Q%)69Q zNh$jP3^DTv5gjwe94Df001Qt}O(pN$yLUv&z#jgS068%+Ap!Jar+OV`<}qeI)(oCW zr_+hW#l?7ke}5o(_nQDfwOUPDmZg|EN<=XLLqdoofVfgBHa|Zf4+^?gvjD*JJV8Vj z5h(z@LWn+Qjsl3@xpOBmIXM~HHoohcFcbl{Z5!LR8_eti*fz#&644fbts6IPl$Mv5 z8(|9INF<^gjfP7^Tg+Sq@ZK1+4xsG1ZgqBcwp=QeLOIKk08pt^$Z;I9ZCfz203d}B zikTfEayB(Zr5yXE`#vZrKfMQhB6KpDbZy(-GR9Q3)hhnklh6&|?i_w>Yvh8a4C7}H5avV049Uakx31vqGNtjS} zToD}t%8gE-?5ZT|7RtG~xxWJy;1kgilnVyI8iYCAIybynO7ltgr#nMagTu_sTS zj2?7adZ?v91Zj4!F<`rgM(^{9x<#OfLty@1EV~RjPBd}}!L#{%h&?AJ1 zDy2R(E{RABAwC*os?VN1`}Xwd)4zT7*6shVdEF|aP5WLWtoD72h_)Xv{0r|{g { - } - } - return true - } - super.onKeyDown(keyCode, event) - } - override def onOptionsItemSelected(item: MenuItem): Boolean = { item.getItemId match { case android.R.id.home => {