Commit 5aae822c authored by Max Lv's avatar Max Lv

add qr code

parent 33ed5c65
...@@ -28,6 +28,8 @@ libraryDependencies ++= Seq( ...@@ -28,6 +28,8 @@ libraryDependencies ++= Seq(
) )
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.joanzapata.android" % "android-iconify" % "1.0.9",
"net.glxn.qrgen" % "android" % "2.0",
"net.simonvt.menudrawer" % "menudrawer" % "3.0.6", "net.simonvt.menudrawer" % "menudrawer" % "3.0.6",
"com.google.android.gms" % "play-services-base" % "6.5.87", "com.google.android.gms" % "play-services-base" % "6.5.87",
"com.google.android.gms" % "play-services-ads" % "6.5.87", "com.google.android.gms" % "play-services-ads" % "6.5.87",
...@@ -48,6 +50,7 @@ proguardOptions in Android ++= Seq("-keep class android.support.v4.app.** { *; } ...@@ -48,6 +50,7 @@ proguardOptions in Android ++= Seq("-keep class android.support.v4.app.** { *; }
"-keep class org.jraf.android.backport.** { *; }", "-keep class org.jraf.android.backport.** { *; }",
"-keep class com.github.shadowsocks.** { *; }", "-keep class com.github.shadowsocks.** { *; }",
"-keep class * extends com.j256.ormlite.** { *; }", "-keep class * extends com.j256.ormlite.** { *; }",
"-keep class com.joanzapata.** { *; }",
"-keepattributes *Annotation*", "-keepattributes *Annotation*",
"-dontwarn org.xbill.**", "-dontwarn org.xbill.**",
"-dontwarn com.actionbarsherlock.**") "-dontwarn com.actionbarsherlock.**")
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
<string name="stopping">正在关闭...</string> <string name="stopping">正在关闭...</string>
<string name="vpn_error">后台服务启动失败:%s</string> <string name="vpn_error">后台服务启动失败:%s</string>
<string name="ok_iknow">确定</string> <string name="ok_iknow">确定</string>
<string name="close">关闭</string>
<string name="port_alert">本地端口号应大于1024</string> <string name="port_alert">本地端口号应大于1024</string>
<string name="port_empty">端口号不能为空</string> <string name="port_empty">端口号不能为空</string>
<string name="proxy_empty">代理服务器地址不能为空</string> <string name="proxy_empty">代理服务器地址不能为空</string>
...@@ -67,6 +68,7 @@ ...@@ -67,6 +68,7 @@
<string name="about_title">影梭(shadowsocks)%s</string> <string name="about_title">影梭(shadowsocks)%s</string>
<string name="flush_dnscache">刷新 DNS 缓存</string> <string name="flush_dnscache">刷新 DNS 缓存</string>
<string name="flushing">刷新中...</string> <string name="flushing">刷新中...</string>
<string name="qrcode">二维码</string>
<string name="add_profile">添加配置文件</string> <string name="add_profile">添加配置文件</string>
<string name="enable_nat">启用 NAT 模式.</string> <string name="enable_nat">启用 NAT 模式.</string>
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
<!-- alert category --> <!-- alert category -->
<string name="ok_iknow">OK</string> <string name="ok_iknow">OK</string>
<string name="close">Close</string>
<string name="port_alert">The local port number should be greater than 1024</string> <string name="port_alert">The local port number should be greater than 1024</string>
<string name="port_empty">Port should not be empty</string> <string name="port_empty">Port should not be empty</string>
<string name="proxy_empty">Proxy should not be empty</string> <string name="proxy_empty">Proxy should not be empty</string>
...@@ -84,6 +85,7 @@ ...@@ -84,6 +85,7 @@
<string name="recovery">Reset</string> <string name="recovery">Reset</string>
<string name="about">About</string> <string name="about">About</string>
<string name="about_title">Shadowsocks %s</string> <string name="about_title">Shadowsocks %s</string>
<string name="qrcode">QR Code</string>
<string name="flush_dnscache">Flush DNS Cache</string> <string name="flush_dnscache">Flush DNS Cache</string>
<string name="flushing">Flushing</string> <string name="flushing">Flushing</string>
<string name="add_profile">Add Profile</string> <string name="add_profile">Add Profile</string>
......
...@@ -41,6 +41,7 @@ package com.github.shadowsocks ...@@ -41,6 +41,7 @@ package com.github.shadowsocks
import java.util import java.util
import java.io.{OutputStream, InputStream, ByteArrayInputStream, ByteArrayOutputStream, IOException, FileOutputStream} import java.io.{OutputStream, InputStream, ByteArrayInputStream, ByteArrayOutputStream, IOException, FileOutputStream}
import java.util.Locale import java.util.Locale
import java.lang.Math
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.app.{Activity, AlertDialog, ProgressDialog} import android.app.{Activity, AlertDialog, ProgressDialog}
...@@ -66,6 +67,10 @@ import com.google.android.gms.analytics.HitBuilders ...@@ -66,6 +67,10 @@ import com.google.android.gms.analytics.HitBuilders
import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentIntegrator
import com.nostra13.universalimageloader.core.download.BaseImageDownloader import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import net.simonvt.menudrawer.MenuDrawer import net.simonvt.menudrawer.MenuDrawer
import com.joanzapata.android.iconify.Iconify
import com.joanzapata.android.iconify.Iconify.IconValue
import com.joanzapata.android.iconify.IconDrawable
import net.glxn.qrgen.android.QRCode
import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.concurrent.ops._ import scala.concurrent.ops._
...@@ -711,7 +716,7 @@ class Shadowsocks ...@@ -711,7 +716,7 @@ class Shadowsocks
def getProfileList: List[Item] = { def getProfileList: List[Item] = {
val list = profileManager.getAllProfiles getOrElse List[Profile]() val list = profileManager.getAllProfiles getOrElse List[Profile]()
list.map(p => new Item(p.id, p.name, -1, updateProfile, delProfile)) list.map(p => new IconItem(p.id, p.name, -1, updateProfile, delProfile))
} }
def getMenuList: List[Any] = { def getMenuList: List[Any] = {
...@@ -723,11 +728,13 @@ class Shadowsocks ...@@ -723,11 +728,13 @@ class Shadowsocks
buf ++= getProfileList buf ++= getProfileList
buf += buf +=
new Item(-400, getString(R.string.add_profile), android.R.drawable.ic_menu_add, newProfile) 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 Category(getString(R.string.settings))
buf += new Item(-100, getString(R.string.recovery), android.R.drawable.ic_menu_revert, _ => { buf += new DrawableItem(-100, getString(R.string.recovery), new IconDrawable(this, IconValue.fa_recycle)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event // send event
application.tracker.send(new HitBuilders.EventBuilder() application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG) .setCategory(Shadowsocks.TAG)
...@@ -738,7 +745,8 @@ class Shadowsocks ...@@ -738,7 +745,8 @@ class Shadowsocks
}) })
buf += buf +=
new Item(-200, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, _ => { new DrawableItem(-200, getString(R.string.flush_dnscache), new IconDrawable(this, IconValue.fa_refresh)
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
// send event // send event
application.tracker.send(new HitBuilders.EventBuilder() application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG) .setCategory(Shadowsocks.TAG)
...@@ -748,7 +756,20 @@ class Shadowsocks ...@@ -748,7 +756,20 @@ class Shadowsocks
flushDnsCache() flushDnsCache()
}) })
buf += new Item(-300, getString(R.string.about), android.R.drawable.ic_menu_info_details, _ => { 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 // send event
application.tracker.send(new HitBuilders.EventBuilder() application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(Shadowsocks.TAG) .setCategory(Shadowsocks.TAG)
...@@ -881,6 +902,31 @@ class Shadowsocks ...@@ -881,6 +902,31 @@ class Shadowsocks
} }
} }
private def dp2px(dp: Int): Int = {
val displayMetrics = getBaseContext.getResources.getDisplayMetrics()
Math.round(dp * (displayMetrics.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)
.setTitle(getString(R.string.qrcode))
.setCancelable(true)
.setNegativeButton(getString(R.string.close), new DialogInterface.OnClickListener() {
override def onClick(dialog: DialogInterface, id: Int) {
dialog.cancel()
}
})
.setView(image)
.create()
.show()
}
private def flushDnsCache() { private def flushDnsCache() {
val h = showProgress(getString(R.string.flushing)) val h = showProgress(getString(R.string.flushing))
spawn { spawn {
......
...@@ -45,11 +45,24 @@ import android.view.View ...@@ -45,11 +45,24 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
import android.widget.TextView import android.widget.TextView
import android.graphics.drawable.Drawable
import com.github.shadowsocks.{R} import com.github.shadowsocks.{R}
import android.view.View.{OnLongClickListener, OnClickListener} import android.view.View.{OnLongClickListener, OnClickListener}
case class Item(id: Int, title: String, iconRes: Int, 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) click: Int => Unit, longClick: Int => Boolean = _ => false)
extends Item
case class Category(title: String) case class Category(title: String)
...@@ -89,7 +102,8 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter { ...@@ -89,7 +102,8 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter {
@Override override def getItemViewType(position: Int): Int = { @Override override def getItemViewType(position: Int): Int = {
getItem(position) match { getItem(position) match {
case Category => MenuAdapter.CATEGORY case Category => MenuAdapter.CATEGORY
case Item => MenuAdapter.ITEM case IconItem => MenuAdapter.ITEM
case DrawableItem => MenuAdapter.ITEM
case _ => MenuAdapter.UNDEFINED case _ => MenuAdapter.UNDEFINED
} }
} }
...@@ -121,9 +135,18 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter { ...@@ -121,9 +135,18 @@ class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter {
} }
val tv: TextView = v.asInstanceOf[TextView] val tv: TextView = v.asInstanceOf[TextView]
tv.setText(value.title) tv.setText(value.title)
if (value.iconRes != -1) {
tv.setCompoundDrawablesWithIntrinsicBounds(value.iconRes, 0, 0, 0) 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(new OnClickListener { tv.setOnClickListener(new OnClickListener {
def onClick(view: View) { def onClick(view: View) {
val item = view.getTag(R.id.mdItem).asInstanceOf[Item] val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
......
...@@ -40,11 +40,19 @@ ...@@ -40,11 +40,19 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import java.util.Locale
import android.net.Uri import android.net.Uri
import android.util.{Log, Base64} import android.util.{Log, Base64}
object Parser { object Parser {
val TAG = "ShadowParser" val TAG = "ShadowParser"
def generate (profile: Profile): String = {
val path = "%s:%s@%s:%d".formatLocal(Locale.ENGLISH,
profile.method, profile.password, profile.host, profile.remotePort)
return "ss://" + Base64.encodeToString(path.getBytes, Base64.NO_PADDING)
}
def parse (data: String): Option[Profile] = { def parse (data: String): Option[Profile] = {
try { try {
Log.d(TAG, data) Log.d(TAG, data)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment