Commit 6dede5c0 authored by Mygod's avatar Mygod

Import/export one/all ss urls

parent fb3f7429
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/action_export"
android:title="@string/action_export"
android:showAsAction="never" />
<item android:id="@+id/action_import" <item android:id="@+id/action_import"
android:title="@string/action_import" android:title="@string/action_import"
android:showAsAction="never" /> android:showAsAction="never"
android:alphabeticShortcut="i"
android:numericShortcut="1"/>
<item android:id="@+id/action_export"
android:title="@string/action_export"
android:showAsAction="never"
android:alphabeticShortcut="e"
android:numericShortcut="2"/>
</menu> </menu>
...@@ -15,6 +15,14 @@ ...@@ -15,6 +15,14 @@
android:title="@string/add_profile_methods_manual_settings" android:title="@string/add_profile_methods_manual_settings"
android:alphabeticShortcut="m" android:alphabeticShortcut="m"
android:numericShortcut="2"/> android:numericShortcut="2"/>
<item android:id="@+id/action_import"
android:title="@string/action_import"
android:alphabeticShortcut="i"
android:numericShortcut="3"/>
<item android:id="@+id/action_export"
android:title="@string/action_export"
android:alphabeticShortcut="e"
android:numericShortcut="4"/>
</menu> </menu>
</item> </item>
</menu> </menu>
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
<string name="profile_manager_dialog_content">&#8226; 点击选择配置文件;\n&#8226; 滑动删除;\n&#8226; <string name="profile_manager_dialog_content">&#8226; 点击选择配置文件;\n&#8226; 滑动删除;\n&#8226;
长按拖动改变顺序。</string> 长按拖动改变顺序。</string>
<string name="gotcha">好的</string> <string name="gotcha">好的</string>
<string name="copy_url">复制 URL</string>
<string name="add_profile_dialog">为影梭添加此配置文件?</string> <string name="add_profile_dialog">为影梭添加此配置文件?</string>
<string name="add_profile_methods_scan_qr_code">扫描二维码</string> <string name="add_profile_methods_scan_qr_code">扫描二维码</string>
<string name="add_profile_methods_manual_settings">手动设置</string> <string name="add_profile_methods_manual_settings">手动设置</string>
......
...@@ -113,6 +113,7 @@ ...@@ -113,6 +113,7 @@
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string> <string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
<string name="add_profile_methods_scan_qr_code">Scan QR code</string> <string name="add_profile_methods_scan_qr_code">Scan QR code</string>
<string name="add_profile_methods_manual_settings">Manual Settings</string> <string name="add_profile_methods_manual_settings">Manual Settings</string>
<string name="copy_url">Copy URL</string>
<plurals name="removed"> <plurals name="removed">
<item quantity="one">Removed</item> <item quantity="one">Removed</item>
<item quantity="other">%d items removed</item> <item quantity="other">%d items removed</item>
......
...@@ -154,26 +154,22 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -154,26 +154,22 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
return true return true
case R.id.action_import => case R.id.action_import =>
if (clipboard.hasPrimaryClip) { if (clipboard.hasPrimaryClip) {
val clipdata = clipboard.getPrimaryClip val proxiedAppSequence = clipboard.getPrimaryClip.getItemAt(0).getText
val label = clipdata.getDescription.getLabel if (proxiedAppSequence != null) {
if (label == Key.proxied) { val proxiedAppString = proxiedAppSequence.toString
val proxiedAppSequence = clipdata.getItemAt(0).getText if (!proxiedAppString.isEmpty) {
if (proxiedAppSequence != null) { val editor = prefs.edit
val proxiedAppString = proxiedAppSequence.toString val i = proxiedAppString.indexOf('\n')
if (!proxiedAppString.isEmpty) { if (i < 0)
val editor = prefs.edit editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply()
val i = proxiedAppString.indexOf('\n') else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean)
if (i < 0) .putString(Key.proxied, proxiedAppString.substring(i + 1)).apply()
editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply() Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean) // Restart activity
.putString(Key.proxied, proxiedAppString.substring(i + 1)).apply() appListView.setVisibility(View.GONE)
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() loadingView.setVisibility(View.VISIBLE)
// Restart activity reloadApps()
appListView.setVisibility(View.GONE) return true
loadingView.setVisibility(View.VISIBLE)
reloadApps()
return true
}
} }
} }
} }
......
package com.github.shadowsocks package com.github.shadowsocks
import android.content.Intent import android.content._
import android.os.{Bundle, Handler} import android.os.{Bundle, Handler}
import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
...@@ -32,17 +32,20 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -32,17 +32,20 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
{ {
val qrcode = itemView.findViewById(R.id.qrcode) val qrcode = itemView.findViewById(R.id.qrcode)
qrcode.setOnClickListener((v: View) => { qrcode.setOnClickListener(_ => {
val url = Parser.generate(item)
val image = new ImageView(ProfileManagerActivity.this) val image = new ImageView(ProfileManagerActivity.this)
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)) 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)) .withSize(Utils.dpToPx(ProfileManagerActivity.this, 250), Utils.dpToPx(ProfileManagerActivity.this, 250))
.asInstanceOf[QRCode].bitmap() .asInstanceOf[QRCode].bitmap()
image.setImageBitmap(qrcode) image.setImageBitmap(qrcode)
new AlertDialog.Builder(ProfileManagerActivity.this) new AlertDialog.Builder(ProfileManagerActivity.this)
.setCancelable(true) .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) .setView(image)
.create() .create()
.show() .show()
...@@ -144,6 +147,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -144,6 +147,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
private lazy val profilesAdapter = new ProfilesAdapter private lazy val profilesAdapter = new ProfilesAdapter
private var undoManager: UndoSnackbarManager[Profile] = _ private var undoManager: UndoSnackbarManager[Profile] = _
private lazy val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager]
override def onCreate(savedInstanceState: Bundle) { override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.layout_profiles) setContentView(R.layout.layout_profiles)
...@@ -216,7 +221,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -216,7 +221,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
Parser.findAll(scanResult.getContents).foreach(ShadowsocksApplication.profileManager.createProfile) 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 => case R.id.scan_qr_code =>
val integrator = new IntentIntegrator(this) val integrator = new IntentIntegrator(this)
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN) val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
...@@ -229,6 +234,25 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -229,6 +234,25 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
ShadowsocksApplication.switchProfile(ShadowsocksApplication.profileManager.save.id) ShadowsocksApplication.switchProfile(ShadowsocksApplication.profileManager.save.id)
finish finish
true 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 case _ => false
} }
} }
...@@ -46,12 +46,12 @@ import com.github.shadowsocks.database.Profile ...@@ -46,12 +46,12 @@ import com.github.shadowsocks.database.Profile
object Parser { object Parser {
val TAG = "ShadowParser" 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, 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 content = new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")
val methodIdx = content.indexOf(':') val methodIdx = content.indexOf(':')
val passwordIdx = content.lastIndexOf('@') val passwordIdx = content.lastIndexOf('@')
......
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