Import/export one/all ss urls
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
<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"
|
||||
android:title="@string/action_import"
|
||||
android:showAsAction="never" />
|
||||
android:title="@string/action_import"
|
||||
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>
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
android:title="@string/add_profile_methods_manual_settings"
|
||||
android:alphabeticShortcut="m"
|
||||
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>
|
||||
</item>
|
||||
</menu>
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<string name="profile_manager_dialog_content">• 点击选择配置文件;\n• 滑动删除;\n•
|
||||
长按拖动改变顺序。</string>
|
||||
<string name="gotcha">好的</string>
|
||||
<string name="copy_url">复制 URL</string>
|
||||
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
|
||||
<string name="add_profile_methods_scan_qr_code">扫描二维码</string>
|
||||
<string name="add_profile_methods_manual_settings">手动设置</string>
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
<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_manual_settings">Manual Settings</string>
|
||||
<string name="copy_url">Copy URL</string>
|
||||
<plurals name="removed">
|
||||
<item quantity="one">Removed</item>
|
||||
<item quantity="other">%d items removed</item>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('@')
|
||||
|
||||
Reference in New Issue
Block a user