Commit 51e155c9 authored by Mygod's avatar Mygod

Add more material design (3)

parent 1f695b9c
......@@ -29,13 +29,10 @@ libraryDependencies ++= Seq(
"com.github.kevinsawicki" % "http-request" % "6.0",
"commons-net" % "commons-net" % "3.3",
"com.google.zxing" % "android-integration" % "3.2.1",
"com.joanzapata.android" % "android-iconify" % "1.0.11",
"net.glxn.qrgen" % "android" % "2.0",
"net.simonvt.menudrawer" % "menudrawer" % "3.0.6",
"com.google.android.gms" % "play-services-base" % "8.1.0",
"com.google.android.gms" % "play-services-ads" % "8.1.0",
"com.google.android.gms" % "play-services-analytics" % "8.1.0",
"com.android.support" % "support-v4" % "23.1.0",
"com.android.support" % "design" % "23.1.0",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.9.4",
......@@ -46,13 +43,9 @@ libraryDependencies ++= Seq(
proguardOptions in Android ++= Seq("-keep class android.support.v4.app.** { *; }",
"-keep interface android.support.v4.app.** { *; }",
"-keep class android.support.v7.widget.Toolbar { <init>(...); }",
"-keep class com.actionbarsherlock.** { *; }",
"-keep interface com.actionbarsherlock.** { *; }",
"-keep class org.jraf.android.backport.** { *; }",
"-keep class com.github.shadowsocks.** { *; }",
"-keep class * extends com.j256.ormlite.** { *; }",
"-keep class com.joanzapata.** { *; }",
"-keepattributes *Annotation*",
"-dontwarn com.google.android.gms.internal.zzhu",
"-dontwarn org.xbill.**",
"-dontwarn com.actionbarsherlock.**")
"-dontwarn org.xbill.**")
......@@ -22,7 +22,8 @@
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent"
android:label="@string/app_name">
android:label="@string/app_name"
android:theme="@style/Theme.Material">
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
......@@ -36,7 +37,6 @@
<activity
android:name=".Shadowsocks"
android:label="@string/app_name"
android:theme="@style/Theme.Shadowsocks"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
......@@ -62,11 +62,22 @@
android:launchMode="singleTask">
</activity>
<activity
android:name=".ProfileManagerActivity"
android:label="@string/app_name"
android:parentActivityName=".Shadowsocks"
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.ProfileManagerActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".AppManager"
android:theme="@style/Theme.Material"
android:label="@string/app_name"
android:parentActivityName=".Shadowsocks">
android:parentActivityName=".Shadowsocks"
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.AppManager"/>
<category android:name="android.intent.category.DEFAULT"/>
......
Subproject commit 095aa7490cb39d513180d1f756e6751107511f98
Subproject commit 890e880dc61ad4f2e9a340a78c797c4aabee6429
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_light_dark" />
<android.support.v7.widget.RecyclerView android:id="@+id/profilesList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on: https://github.com/android/platform_frameworks_base/blob/master/core/res/res/layout/select_dialog_singlechoice_material.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="20dp"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingLeft="20dp"
android:paddingRight="?attr/dialogPreferredPadding">
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorAlertDialogListItem"
android:gravity="center_vertical"
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
android:drawableLeft="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="20dp"
android:maxLines="4"
android:scrollHorizontally="false" />
<!-- TODO: Replace this view with an ImageView -->
<ImageView android:id="@+id/qrcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@android:drawable/ic_menu_add"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/qrcode"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Category" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Title" />
\ No newline at end of file
......@@ -7,6 +7,5 @@
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:titleTextAppearance="@style/Toolbar.TitleText"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:id="@+id/toolbar" />
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Todo: replace this placeholder icon with a material one -->
<item android:title="@string/add_profile"
android:icon="@android:drawable/ic_menu_add"
android:alphabeticShortcut="a"
android:numericShortcut="0"
app:showAsAction="always">
<menu>
<item android:id="@+id/scan_qr_code"
android:title="@string/add_profile_methods_scan_qr_code"
android:alphabeticShortcut="s"
android:numericShortcut="1"/>
<item android:id="@+id/manual_settings"
android:title="@string/add_profile_methods_manual_settings"
android:alphabeticShortcut="m"
android:numericShortcut="2"/>
</menu>
</item>
</menu>
......@@ -3,9 +3,6 @@
<resources>
<string name="app_name">影梭</string>
<string name="screen_name">shadowsocks</string>
<string name="function_cat">服务控制</string>
<string name="service_controller">代理开关</string>
<string name="service_summary">启动/关闭后台服务</string>
<string name="proxy_cat">服务器设置</string>
<string name="profile">配置文件</string>
<string name="profile_summary">配置文件名称</string>
......@@ -39,26 +36,18 @@
<string name="traffic_stat_summary">在通知栏上显示当前上下行速率</string>
<string name="forward_success">后台服务已开始运行。</string>
<string name="service_running">已连接到「%s」</string>
<string name="service_status" formatted="false">当前速率:%d KB/s</string>
<string name="forward_fail">后台服务启动失败。</string>
<string name="service_stopped">后台服务已停止。</string>
<string name="forward_stop">后台服务已停止。</string>
<string name="service_failed">无法连接远程服务器</string>
<string name="stop">停止服务</string>
<string name="stopping">正在关闭...</string>
<string name="vpn_error">后台服务启动失败:%s</string>
<string name="ok_iknow">确定</string>
<string name="close">关闭</string>
<string name="port_alert">本地端口号应大于1024</string>
<string name="port_empty">端口号不能为空</string>
<string name="proxy_empty">代理服务器地址不能为空</string>
<string name="crash_alert">检测到意外退出,状态已被重置。</string>
<string name="connect">连接</string>
<string name="initializing">正在初始化...</string>
<string name="recovering">重置中...</string>
<string name="loading">正在加载...</string>
<string name="yes"></string>
<string name="no"></string>
<string name="profiles">配置文件</string>
<string name="settings">设置选项</string>
<string name="remove_profile">删除此配置文件「%s」?</string>
......@@ -84,10 +73,8 @@
<!-- profile -->
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
<string-array name="add_profile_methods">
<item>扫描二维码</item>
<item>手动设置</item>
</string-array>
<string name="add_profile_methods_scan_qr_code">扫描二维码</string>
<string name="add_profile_methods_manual_settings">手动设置</string>
<string-array name="route_entry">
<item>全局</item>
......@@ -101,6 +88,8 @@
<string name="action_export_err">导出失败</string>
<string name="action_import_msg">导入成功</string>
<string name="action_import_err">导入失败</string>
<string name="error">错误</string>
<string name="removed">已删除</string>
<string name="undo">撤销</string>
</resources>
......@@ -4,15 +4,14 @@
<string name="app_name">Shadowsocks</string>
<string name="screen_name">shadowsocks</string>
<!-- function category -->
<string name="function_cat">Service Controller</string>
<string name="service_controller">Proxy Switch</string>
<string name="service_summary">Enable / Disable Proxy</string>
<!-- global category -->
<string name="global_cat">Global Settings</string>
<string name="profile">Profile</string>
<string name="profile_summary">Switch to another profile</string>
<!-- proxy category -->
<string name="proxy_cat">Server Settings</string>
<string name="profile">Profile</string>
<string name="profile_summary">Profile name</string>
<string name="profile_name">Profile name</string>
<string name="proxy">Server</string>
<string name="proxy_summary">Hostname or IP of the remote server</string>
<string name="remote_port">Remote Port</string>
......@@ -54,26 +53,18 @@
<!-- notification category -->
<string name="forward_success">Shadowsocks started.</string>
<string name="service_running">Connected to "%s"</string>
<string name="service_status" formatted="false">Current Rate: %d KB/s</string>
<string name="forward_fail">Shadowsocks failed to start.</string>
<string name="service_stopped">Shadowsocks stopped.</string>
<string name="forward_stop">Shadowsocks stopped.</string>
<string name="service_failed">Failed to connect the remote server</string>
<string name="stop">Stop the service</string>
<string name="stopping">Shutting down...</string>
<string name="vpn_error">%s</string>
<string name="enable_nat">Turn on NAT mode.</string>
<string name="disable_nat">Turn off NAT mode.</string>
<string name="error">ERROR</string>
<!-- alert category -->
<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_empty">Port should not be empty</string>
<string name="proxy_empty">Proxy should not be empty</string>
<string name="crash_alert">An unexpected exit detected, the context has been reset.
</string>
<string name="connect">Connect</string>
<string name="initializing">Initializing…</string>
<string name="recovering">Resetting…</string>
......@@ -101,9 +92,9 @@
<!-- profile -->
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
<string-array name="add_profile_methods">
<item>Scan QR code</item>
<item>Manual Settings</item>
</string-array>
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
<string name="add_profile_methods_manual_settings">Manual Settings</string>
<string name="removed">Removed</string>
<string name="undo">Undo</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!--
Zirco Browser for Android
Copyright (C) 2010 - 2011 J. Devauchelle and contributors.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation.
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.
-->
<resources>
<style name="Toolbar.TitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<style name="Toolbar.Logo" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">24sp</item>
</style>
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
......@@ -51,45 +37,4 @@
<item name="android:windowIsTranslucent">true</item>
</style>
<!-- Variation on the Light theme that turns off the title -->
<style name="Theme.Shadowsocks" parent="Theme.Material">
<item name="menuDrawerStyle">@style/MenuDrawerStyle</item>
</style>
<style name="MenuDrawer"/>
<style name="MenuDrawer.Widget"/>
<style name="MenuDrawer.Widget.Category">
<item name="android:textStyle">bold</item>
<item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:background">@drawable/md__category_background</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
</style>
<style name="MenuDrawer.Widget.Title">
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:textAppearance">?android:attr/textAppearance</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textSize">18sp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">32dp</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:gravity">center_vertical</item>
</style>
<style name="MenuDrawerStyle" parent="Widget.MenuDrawer">
<item name="mdActiveIndicator">@drawable/menu_arrow</item>
<item name="mdMenuSize">250dp</item>
</style>
</resources>
......@@ -2,50 +2,58 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<PreferenceCategory android:title="@string/global_cat">
<Preference android:summary="@string/profile_summary" android:title="@string/profile">
<intent android:action="com.github.shadowsocks.ProfileManagerActivity"/>
</Preference>
<!-- TODO: VPN/NAT SwitchPreference -->
<SwitchPreference
android:key="isAutoConnect"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
<Preference android:key="recovery" android:title="@string/recovery"/>
<Preference android:key="flush_dnscache" android:title="@string/flush_dnscache"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/proxy_cat">
<com.github.shadowsocks.preferences.ProfileEditTextPreference
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="Default"
android:key="profileName"
android:summary="@string/profile_summary"
android:title="@string/profile">
</com.github.shadowsocks.preferences.ProfileEditTextPreference>
android:title="@string/profile_name"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="198.199.101.152"
android:key="proxy"
android:summary="@string/proxy_summary"
android:title="@string/proxy">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/proxy"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:inputType="number"
android:defaultValue="443"
android:key="remotePort"
android:summary="@string/remote_port_summary"
android:title="@string/remote_port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/remote_port"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:inputType="number"
android:defaultValue="1080"
android:key="port"
android:summary="@string/port_summary"
android:title="@string/port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/port"/>
<com.github.shadowsocks.preferences.PasswordEditTextPreference
android:inputType="textPassword"
android:defaultValue="u1rRWTssNv0p"
android:key="sitekey"
android:summary="@string/sitekey_summary"
android:title="@string/sitekey">
</com.github.shadowsocks.preferences.PasswordEditTextPreference>
android:title="@string/sitekey"/>
<com.github.shadowsocks.preferences.DropDownPreference
android:defaultValue="rc4"
android:key="encMethod"
app:entries="@array/enc_method_entry"
app:entryValues="@array/enc_method_value"
app:summary="%s"
android:title="@string/enc_method">
</com.github.shadowsocks.preferences.DropDownPreference>
android:title="@string/enc_method"/>
</PreferenceCategory>
......@@ -58,21 +66,18 @@
app:entries="@array/route_entry"
app:entryValues="@array/route_value"
app:summary="%s"
android:title="@string/route_list">
</com.github.shadowsocks.preferences.DropDownPreference>
android:title="@string/route_list"/>
<SwitchPreference
android:key="isIpv6"
android:defaultValue="false"
android:summary="@string/ipv6_summary"
android:title="@string/ipv6">
</SwitchPreference>
android:title="@string/ipv6"/>
<SwitchPreference
android:defaultValue="true"
android:key="isGlobalProxy"
android:disableDependentsState="true"
android:summary="@string/auto_set_proxy_summary"
android:title="@string/auto_set_proxy">
</SwitchPreference>
android:title="@string/auto_set_proxy"/>
<Preference
android:key="proxyedApps"
android:dependency="isGlobalProxy"
......@@ -84,19 +89,12 @@
android:key="isUdpDns"
android:defaultValue="false"
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns">
</SwitchPreference>
android:title="@string/udp_dns"/>
<SwitchPreference
android:key="isAuth"
android:defaultValue="false"
android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth">
</SwitchPreference>
<SwitchPreference
android:key="isAutoConnect"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect">
</SwitchPreference>
android:title="@string/onetime_auth"/>
</PreferenceCategory>
......
......@@ -39,32 +39,26 @@
package com.github.shadowsocks
import android.app.{Activity, ProgressDialog}
import android.content.ClipboardManager
import android.content.ClipData
import android.content.Context
import android.content.SharedPreferences
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream}
import android.app.ProgressDialog
import android.content.{ClipData, ClipboardManager, Context, SharedPreferences}
import android.content.pm.PackageManager
import android.graphics.{Bitmap, PixelFormat}
import android.os.Bundle
import android.os.Handler
import android.os.{Bundle, Handler}
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.WindowManager
import android.widget._
import android.view.{Menu, MenuItem, View, ViewGroup, WindowManager}
import android.widget.AbsListView.OnScrollListener
import android.widget.CompoundButton.OnCheckedChangeListener
import android.widget._
import com.github.shadowsocks.utils.{Key, Scheme, Utils}
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import java.io.{ByteArrayOutputStream, ByteArrayInputStream, InputStream}
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
......@@ -105,7 +99,7 @@ object AppManager {
}
}
class AppManager extends Activity with OnCheckedChangeListener with OnClickListener {
class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnClickListener {
val MSG_LOAD_START = 1
val MSG_LOAD_FINISH = 2
......
......@@ -57,14 +57,14 @@ class ParserActivity extends Activity {
new AlertDialog.Builder(this)
.setTitle(R.string.add_profile_dialog)
.setCancelable(false)
.setPositiveButton(R.string.yes, ((dialog: DialogInterface, id: Int) => {
.setPositiveButton(android.R.string.yes, ((dialog: DialogInterface, id: Int) => {
Parser.parse(data) match {
case Some(profile) => addProfile(profile)
case _ => // ignore
}
dialog.dismiss()
}): DialogInterface.OnClickListener)
.setNegativeButton(R.string.no, ((dialog: DialogInterface, id: Int) => {
.setNegativeButton(android.R.string.no, ((dialog: DialogInterface, id: Int) => {
dialog.dismiss()
finish()
}): DialogInterface.OnClickListener)
......@@ -90,7 +90,7 @@ class ParserActivity extends Activity {
h.postDelayed(() => {
val profileManager =
new ProfileManager(PreferenceManager.getDefaultSharedPreferences(getBaseContext),
getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
ShadowsocksApplication.dbHelper)
profileManager.createOrUpdateProfile(profile)
profileManager.reload(profile.id)
h.sendEmptyMessage(0)
......
package com.github.shadowsocks
import android.app.AlertDialog
import android.content.{DialogInterface, Intent}
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.support.v7.widget.helper.ItemTouchHelper
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
import android.widget.{LinearLayout, ImageView, CheckedTextView, Toast}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Parser, Utils}
import com.google.zxing.integration.android.IntentIntegrator
import net.glxn.qrgen.android.QRCode
import scala.collection.mutable.ArrayBuffer
/**
* @author Mygod
*/
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener {
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
itemView.setOnClickListener(this)
{
val qrcode = itemView.findViewById(R.id.qrcode)
qrcode.setOnClickListener((v: View) => {
val image = new ImageView(ProfileManagerActivity.this)
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
val qrcode = QRCode.from(Parser.generate(item))
.withSize(Utils.dpToPx(ProfileManagerActivity.this, 250), Utils.dpToPx(ProfileManagerActivity.this, 250))
.asInstanceOf[QRCode]
image.setImageBitmap(qrcode.bitmap())
new AlertDialog.Builder(ProfileManagerActivity.this)
.setCancelable(true)
.setNegativeButton(getString(R.string.close),
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
.setView(image)
.create()
.show()
})
qrcode.setOnLongClickListener((v: View) => {
Utils.positionToast(Toast.makeText(ProfileManagerActivity.this, R.string.qrcode, Toast.LENGTH_SHORT), qrcode,
getWindow, 0, Utils.dpToPx(ProfileManagerActivity.this, 8)).show
true
})
}
def bind(item: Profile) {
text.setText(item.name)
text.setChecked(item.id == ShadowsocksApplication.profileId)
this.item = item
}
def onClick(v: View) = {
ShadowsocksApplication.profileId(item.id)
finish
}
}
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
private val recycleBin = new ArrayBuffer[(Int, Profile)]
private var profiles = new ArrayBuffer[Profile]
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
def getItemCount = profiles.length
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i))
def onCreateViewHolder(vg: ViewGroup, i: Int) =
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
def add(item: Profile) {
removedSnackbar.dismiss
commitRemoves
val pos = getItemCount
if (ShadowsocksApplication.profileManager.createOrUpdateProfile(item)) {
profiles += item
notifyItemInserted(pos)
}
}
def remove(pos: Int) {
recycleBin.append((pos, profiles(pos)))
profiles.remove(pos)
notifyItemRemoved(pos)
}
def undoRemoves {
for ((index, item) <- recycleBin.reverseIterator) {
profiles.insert(index, item)
notifyItemInserted(index)
}
recycleBin.clear
}
def commitRemoves {
for ((index, item) <- recycleBin) {
ShadowsocksApplication.profileManager.delProfile(item.id)
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
}
recycleBin.clear
}
}
private lazy val profilesAdapter = new ProfilesAdapter
private var removedSnackbar: Snackbar = _
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_profiles)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.profiles)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha)
toolbar.setNavigationOnClickListener((v: View) => {
val intent = getParentActivityIntent
if (intent == null) finish else navigateUpTo(intent)
})
toolbar.inflateMenu(R.menu.add_profile_methods)
toolbar.setOnMenuItemClickListener(this)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
profilesList.setLayoutManager(new LinearLayoutManager(this))
profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter)
removedSnackbar = Snackbar.make(findViewById(android.R.id.content), R.string.removed, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
def onViewAttachedToWindow(v: View) = ()
})
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
profilesAdapter.remove(viewHolder.getAdapterPosition)
removedSnackbar.show
}
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
}).attachToRecyclerView(profilesList)
}
override def onDestroy {
super.onDestroy
profilesAdapter.commitRemoves
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (scanResult != null) Parser.parse(scanResult.getContents) match {
case Some(profile) => profilesAdapter.add(profile)
case _ => // ignore
}
}
def onMenuItemClick(item: MenuItem) = item.getItemId match {
case R.id.scan_qr_code =>
val integrator = new IntentIntegrator(this)
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
list.add("tw.com.quickmark")
integrator.setTargetApplications(list)
integrator.initiateScan()
true
case R.id.manual_settings =>
ShadowsocksApplication.profileManager.reload(-1)
ShadowsocksApplication.profileId(ShadowsocksApplication.profileManager.save.id)
finish
true
case _ => false
}
}
......@@ -43,19 +43,47 @@ import java.util
import java.util.concurrent.TimeUnit
import android.app.Application
import com.github.shadowsocks.database.DBHelper
import com.github.shadowsocks.utils.Utils
import com.google.android.gms.analytics.GoogleAnalytics
import android.content.pm.PackageManager
import android.preference.PreferenceManager
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
import com.github.shadowsocks.utils.{Key, Utils}
import com.google.android.gms.analytics.{HitBuilders, GoogleAnalytics}
import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
object ShadowsocksApplication {
var instance: ShadowsocksApplication = _
lazy val dbHelper = new DBHelper(instance)
final val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = _
lazy val tracker = GoogleAnalytics.getInstance(instance).newTracker(R.xml.tracker)
lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance)
lazy val profileManager = new ProfileManager(settings, dbHelper)
def getVersionName = try {
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
} catch {
case _: PackageManager.NameNotFoundException => "Package name not found"
case _: Throwable => null
}
// send event
def track(category: String, action: String) = tracker.send(new HitBuilders.EventBuilder()
.setAction(action)
.setLabel(getVersionName)
.build())
def profileId = settings.getInt(Key.profileId, -1)
def profileId(i: Int) = settings.edit.putInt(Key.profileId, i).apply
def proxy = settings.getString(Key.proxy, "")
def currentProfile = profileManager.getProfile(profileId)
}
class ShadowsocksApplication extends Application {
lazy val dbHelper = new DBHelper(this)
lazy val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = null
lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
import ShadowsocksApplication._
override def onCreate() {
ShadowsocksApplication.instance = this
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
......
......@@ -40,22 +40,20 @@
package com.github.shadowsocks
import java.io.File
import java.lang.reflect.{InvocationTargetException, Method}
import java.net.{Inet6Address, InetAddress}
import java.util.Locale
import android.app._
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.net.{Network, ConnectivityManager}
import android.net.{ConnectivityManager, Network}
import android.os._
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
import android.util.{SparseArray, Log}
import android.util.{Log, SparseArray}
import android.widget.Toast
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.HitBuilders
import scala.collection._
import scala.collection.mutable.ArrayBuffer
......@@ -77,8 +75,6 @@ class ShadowsocksNatService extends Service with BaseService {
var apps: Array[ProxiedApp] = null
val myUid = Process.myUid()
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
private val dnsAddressCache = new SparseArray[String]
def getNetId(network: Network): Int = {
......@@ -254,8 +250,8 @@ class ShadowsocksNatService extends Service with BaseService {
def startDnsDaemon() {
val conf = if (config.route == Route.BYPASS_CHN) {
val reject = ConfigUtils.getRejectList(getContext, application)
val blackList = ConfigUtils.getBlackList(getContext, application)
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "127.0.0.1", 8153,
Path.BASE + "pdnsd-nat.pid", reject, blackList, 8163, "")
} else {
......@@ -457,19 +453,14 @@ class ShadowsocksNatService extends Service with BaseService {
registerReceiver(lockReceiver, screenFilter)
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("start")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "start")
changeState(State.CONNECTING)
Future {
if (config.proxy == "198.199.101.152") {
val holder = application.containerHolder
val holder = ShadowsocksApplication.containerHolder
try {
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
......@@ -531,12 +522,7 @@ class ShadowsocksNatService extends Service with BaseService {
}
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("stop")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "stop")
// reset NAT
killProcesses()
......
package com.github.shadowsocks
import android.os.Bundle
import android.preference.PreferenceFragment
import android.preference.{Preference, PreferenceFragment}
// TODO: Move related logic here
class ShadowsocksSettings extends PreferenceFragment {
private lazy val activity = getActivity.asInstanceOf[Shadowsocks]
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_all)
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "reset")
activity.recovery()
true
})
findPreference("flush_dnscache").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache")
activity.flushDnsCache()
true
})
}
}
......@@ -53,7 +53,6 @@ import android.util.Log
import android.widget.Toast
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.HitBuilders
import org.apache.commons.net.util.SubnetUtils
import scala.collection.mutable
......@@ -62,8 +61,6 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
class ShadowsocksVpnService extends VpnService with BaseService {
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
val TAG = "ShadowsocksVpnService"
val VPN_MTU = 1500
val PRIVATE_VLAN = "26.26.26.%s"
......@@ -152,12 +149,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
// channge the state
changeState(State.STOPPING)
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("stop")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "stop")
// reset VPN
killProcesses()
......@@ -231,18 +223,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
return
}
// send event
application.tracker.send(new HitBuilders.EventBuilder()
.setCategory(TAG)
.setAction("start")
.setLabel(getVersionName)
.build())
ShadowsocksApplication.track(TAG, "start")
changeState(State.CONNECTING)
Future {
if (config.proxy == "198.199.101.152") {
val holder = getApplication.asInstanceOf[ShadowsocksApplication].containerHolder
val holder = ShadowsocksApplication.containerHolder
try {
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
......@@ -371,8 +358,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val ipv6 = if (config.isIpv6) "" else "reject = ::/0;"
val conf = {
if (config.route == Route.BYPASS_CHN) {
val reject = ConfigUtils.getRejectList(getContext, application)
val blackList = ConfigUtils.getBlackList(getContext, application)
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153,
Path.BASE + "pdnsd-vpn.pid", reject, blackList, 8163, ipv6)
} else {
......
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2014 <max.c.lv@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.database
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{BaseAdapter, TextView}
import com.github.shadowsocks.R
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)
extends Item
case class Category(title: String)
object MenuAdapter {
trait MenuListener {
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 {
def setListener(listener: MenuAdapter.MenuListener) {
this.listener = listener
}
def setActiveId(activeId: Int) {
this.activeId = activeId
}
@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 IconItem => MenuAdapter.ITEM
case DrawableItem => 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)
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((view: View) => {
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
item.click(item.id)
})
tv.setOnLongClickListener((view: View) => {
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
item.longClick(item.id)
})
v.setTag(R.id.mdItem, value)
if (value.id == activeId) {
if (listener != null) listener.onActiveViewChanged(v, position)
}
case _ =>
}
v.setTag(R.id.mdActiveViewPosition, position)
v
}
def updateList(list: List[Any], activeId: Int) {
items = list
this.activeId = activeId
notifyDataSetChanged()
}
private var listener: MenuAdapter.MenuListener = null
private var activeId: Int = -1
}
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.preferences
import android.content.{Intent, Context}
import android.preference.EditTextPreference
import android.util.AttributeSet
import com.github.shadowsocks.utils.Action
class ProfileEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
extends EditTextPreference(context, attrs, defStyle) {
def this(context: Context, attrs: AttributeSet) = {
this(context, attrs, android.R.attr.editTextPreferenceStyle)
mDefaultSummary = getSummary
}
override def setText(text: String) {
super.setText(text)
setSummary(text)
}
override def setSummary(summary: CharSequence) {
if (summary.toString.isEmpty) {
super.setSummary(mDefaultSummary)
} else {
super.setSummary(summary)
}
context.sendBroadcast(new Intent(Action.UPDATE_PREFS))
}
def resetSummary(summary: CharSequence) {
if (summary.toString.isEmpty) {
super.setSummary(mDefaultSummary)
} else {
super.setSummary(summary)
}
}
private var mDefaultSummary: CharSequence = getSummary
}
......@@ -198,14 +198,14 @@ object ConfigUtils {
}
def refresh(context: Context) {
val holder = context.getApplicationContext.asInstanceOf[ShadowsocksApplication].containerHolder
val holder = ShadowsocksApplication.containerHolder
if (holder != null) holder.refresh()
}
def getRejectList(context: Context, app: ShadowsocksApplication): String = {
def getRejectList(context: Context): String = {
val default = context.getString(R.string.reject)
try {
val container = app.containerHolder.getContainer
val container = ShadowsocksApplication.containerHolder.getContainer
val update = container.getString("reject")
if (update == null || update.isEmpty) default else update
} catch {
......@@ -213,10 +213,10 @@ object ConfigUtils {
}
}
def getBlackList(context: Context, app: ShadowsocksApplication): String = {
def getBlackList(context: Context): String = {
val default = context.getString(R.string.black_list)
try {
val container = app.containerHolder.getContainer
val container = ShadowsocksApplication.containerHolder.getContainer
val update = container.getString("black_list")
if (update == null || update.isEmpty) default else update
} catch {
......
......@@ -111,8 +111,6 @@ object State {
object Action {
val SERVICE = "com.github.shadowsocks.SERVICE"
val CLOSE = "com.github.shadowsocks.CLOSE"
val UPDATE_FRAGMENT = "com.github.shadowsocks.ACTION_UPDATE_FRAGMENT"
val UPDATE_PREFS = "com.github.shadowsocks.ACTION_UPDATE_PREFS"
}
object Route {
......
......@@ -92,6 +92,9 @@ object Utils {
new String(Base64.encode(mdg.digest, 0))
}
def dpToPx(context: Context, dp: Int): Int =
Math.round(dp * (context.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
/*
* round or floor depending on whether you are using offsets(floor) or
* widths(round)
......
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