From 6dede5c067ef67aa4aceaf895d2649393f29ec57 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 12 Mar 2016 15:11:11 +0800 Subject: [PATCH] Import/export one/all ss urls --- src/main/res/menu/app_manager_menu.xml | 14 +++++--- src/main/res/menu/profile_manager_menu.xml | 8 +++++ src/main/res/values-zh/strings.xml | 1 + src/main/res/values/strings.xml | 1 + .../com/github/shadowsocks/AppManager.scala | 36 +++++++++---------- .../shadowsocks/ProfileManagerActivity.scala | 34 +++++++++++++++--- .../com/github/shadowsocks/utils/Parser.scala | 6 ++-- 7 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/main/res/menu/app_manager_menu.xml b/src/main/res/menu/app_manager_menu.xml index f6a20e01..ef91b2ce 100644 --- a/src/main/res/menu/app_manager_menu.xml +++ b/src/main/res/menu/app_manager_menu.xml @@ -1,8 +1,12 @@ - + android:title="@string/action_import" + android:showAsAction="never" + android:alphabeticShortcut="i" + android:numericShortcut="1"/> + diff --git a/src/main/res/menu/profile_manager_menu.xml b/src/main/res/menu/profile_manager_menu.xml index 6d080c8f..a5f305df 100644 --- a/src/main/res/menu/profile_manager_menu.xml +++ b/src/main/res/menu/profile_manager_menu.xml @@ -15,6 +15,14 @@ android:title="@string/add_profile_methods_manual_settings" android:alphabeticShortcut="m" android:numericShortcut="2"/> + + diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index bd08a6ef..01274188 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -90,6 +90,7 @@ • 点击选择配置文件;\n• 滑动删除;\n• 长按拖动改变顺序。 好的 + 复制 URL 为影梭添加此配置文件? 扫描二维码 手动设置 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 1c6f63e8..57d5a710 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -113,6 +113,7 @@ Add this Shadowsocks Profile? Scan QR code Manual Settings + Copy URL Removed %d items removed diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index 9f0d9bba..85427dd4 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -154,26 +154,22 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC return true case R.id.action_import => if (clipboard.hasPrimaryClip) { - val clipdata = clipboard.getPrimaryClip - val label = clipdata.getDescription.getLabel - if (label == Key.proxied) { - val proxiedAppSequence = clipdata.getItemAt(0).getText - if (proxiedAppSequence != null) { - val proxiedAppString = proxiedAppSequence.toString - if (!proxiedAppString.isEmpty) { - val editor = prefs.edit - val i = proxiedAppString.indexOf('\n') - if (i < 0) - editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply() - else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean) - .putString(Key.proxied, proxiedAppString.substring(i + 1)).apply() - Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() - // Restart activity - appListView.setVisibility(View.GONE) - loadingView.setVisibility(View.VISIBLE) - reloadApps() - return true - } + val proxiedAppSequence = clipboard.getPrimaryClip.getItemAt(0).getText + if (proxiedAppSequence != null) { + val proxiedAppString = proxiedAppSequence.toString + if (!proxiedAppString.isEmpty) { + val editor = prefs.edit + val i = proxiedAppString.indexOf('\n') + if (i < 0) + editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply() + else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean) + .putString(Key.proxied, proxiedAppString.substring(i + 1)).apply() + Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() + // Restart activity + appListView.setVisibility(View.GONE) + loadingView.setVisibility(View.VISIBLE) + reloadApps() + return true } } } diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 4df76605..a28fe9bd 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -1,6 +1,6 @@ package com.github.shadowsocks -import android.content.Intent +import android.content._ import android.os.{Bundle, Handler} import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.widget.RecyclerView.ViewHolder @@ -32,17 +32,20 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe { val qrcode = itemView.findViewById(R.id.qrcode) - qrcode.setOnClickListener((v: View) => { + qrcode.setOnClickListener(_ => { + val url = Parser.generate(item) val image = new ImageView(ProfileManagerActivity.this) image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)) - val qrcode = QRCode.from(Parser.generate(item)) + val qrcode = QRCode.from(url) .withSize(Utils.dpToPx(ProfileManagerActivity.this, 250), Utils.dpToPx(ProfileManagerActivity.this, 250)) .asInstanceOf[QRCode].bitmap() image.setImageBitmap(qrcode) new AlertDialog.Builder(ProfileManagerActivity.this) .setCancelable(true) - .setNegativeButton(R.string.close, null) + .setPositiveButton(R.string.close, null) + .setNegativeButton(R.string.copy_url, ((_, _) => + clipboard.setPrimaryClip(ClipData.newPlainText(null, url))): DialogInterface.OnClickListener) .setView(image) .create() .show() @@ -144,6 +147,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe private lazy val profilesAdapter = new ProfilesAdapter private var undoManager: UndoSnackbarManager[Profile] = _ + private lazy val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager] + override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.layout_profiles) @@ -216,7 +221,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe Parser.findAll(scanResult.getContents).foreach(ShadowsocksApplication.profileManager.createProfile) } - def onMenuItemClick(item: MenuItem) = item.getItemId match { + def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { case R.id.scan_qr_code => val integrator = new IntentIntegrator(this) val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN) @@ -229,6 +234,25 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ShadowsocksApplication.switchProfile(ShadowsocksApplication.profileManager.save.id) finish true + case R.id.action_import => + if (clipboard.hasPrimaryClip) { + val profiles = Parser.findAll(clipboard.getPrimaryClip.getItemAt(0).getText) + if (profiles.nonEmpty) { + profiles.foreach(ShadowsocksApplication.profileManager.createProfile) + Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show + return true + } + } + Toast.makeText(this, R.string.action_import_err, Toast.LENGTH_SHORT).show + true + case R.id.action_export => + ShadowsocksApplication.profileManager.getAllProfiles match { + case Some(profiles) => + clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.map(Parser.generate).mkString("\n"))) + Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show + case _ => Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show + } + true case _ => false } } diff --git a/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/src/main/scala/com/github/shadowsocks/utils/Parser.scala index 5ffc3eed..3784fc8d 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -46,12 +46,12 @@ import com.github.shadowsocks.database.Profile object Parser { val TAG = "ShadowParser" - private val pattern = "(?i)ss://([A-Za-z0-9+/=]+)".r + private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)".r def generate (profile: Profile): String = "ss://" + Base64.encodeToString("%s:%s@%s:%d".formatLocal(Locale.ENGLISH, - profile.method, profile.password, profile.host, profile.remotePort).getBytes, Base64.NO_PADDING) + profile.method, profile.password, profile.host, profile.remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) - def findAll(data: String) = pattern.findAllMatchIn(data).map(m => try { + def findAll(data: CharSequence) = pattern.findAllMatchIn(data).map(m => try { val content = new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8") val methodIdx = content.indexOf(':') val passwordIdx = content.lastIndexOf('@')