Commit 6725bcc8 authored by Max Lv's avatar Max Lv

Merge pull request #462 from Mygod/master

Refine Tasker features
parents 56a065b1 e5ce8fd0
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include layout="@layout/toolbar_light_dark" /> <include layout="@layout/toolbar_light_dark" />
<FrameLayout <FrameLayout android:layout_width="match_parent"
android:id="@+id/fragment" android:layout_height="wrap_content"
android:elevation="1dp"
android:background="@android:color/white">
<Switch android:id="@+id/serviceSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/toggle_service_state"
android:textSize="18sp"
android:padding="16dp"/>
</FrameLayout>
<android.support.v7.widget.RecyclerView android:id="@+id/profilesList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Switch android:id="@+id/service_switch"
android:checked="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/toggle_service_state"
android:textSize="18sp"
android:textColor="?android:attr/textColorSecondary"
android:padding="16dp"/>
</FrameLayout>
<?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">
<item android:title="@string/save"
android:id="@+id/save"
android:icon="@drawable/ic_check"
android:alphabeticShortcut="s"
android:numericShortcut="0"
app:showAsAction="always"/>
</menu>
\ No newline at end of file
...@@ -97,7 +97,10 @@ ...@@ -97,7 +97,10 @@
<string name="removed">已删除</string> <string name="removed">已删除</string>
<string name="undo">撤销</string> <string name="undo">撤销</string>
<string name="toggle_service_state">切换服务状态</string> <string name="toggle_service_state">启动服务</string>
<string name="save">保存</string> <string name="start_service_default">连接到当前服务器</string>
<string name="start_service">连接到 %s</string>
<string name="stop_service">切换到 %s</string>
<string name="profile_default">使用当前配置</string>
</resources> </resources>
...@@ -226,14 +226,4 @@ ...@@ -226,14 +226,4 @@
<item>204.0.0.0/6</item> <item>204.0.0.0/6</item>
<item>208.0.0.0/4</item> <item>208.0.0.0/4</item>
</string-array> </string-array>
<string-array name="tasker_action_name">
<item>Toggle service</item>
<item>Switch profile</item>
</string-array>
<string-array name="tasker_action_value">
<item>toggle_service</item>
<item>switch_profile</item>
</string-array>
</resources> </resources>
...@@ -103,12 +103,9 @@ ...@@ -103,12 +103,9 @@
<string name="undo">Undo</string> <string name="undo">Undo</string>
<!-- tasker --> <!-- tasker -->
<string name="toggle_service_state">Toggle service state</string> <string name="toggle_service_state">Start the service</string>
<string name="save">Save</string> <string name="start_service_default">Connect to the current server</string>
<string name="turn_service_state">Turn service %s</string> <string name="start_service">Connect to %s</string>
<string name="no_action_selected">No action was selected</string> <string name="stop_service">Switch to %s</string>
<string name="no_profile_selected">No profile was selected</string> <string name="profile_default">Use the current profile</string>
<string name="switch_profile_to">Switch profile to %s</string>
<string name="state_on">on</string>
<string name="state_off">off</string>
</resources> </resources>
...@@ -93,7 +93,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -93,7 +93,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
private val recycleBin = new ArrayBuffer[(Int, Profile)] private val recycleBin = new ArrayBuffer[(Int, Profile)]
private var profiles = new ArrayBuffer[Profile] private var profiles = new ArrayBuffer[Profile]
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]()) profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
def getItemCount = profiles.length def getItemCount = profiles.length
......
...@@ -39,183 +39,80 @@ ...@@ -39,183 +39,80 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.res.Resources
import android.os.Bundle import android.os.{Build, Bundle}
import android.support.v4.app.{Fragment, ListFragment}
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
import android.util.Log
import android.view.{LayoutInflater, View, ViewGroup} import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.AdapterView.OnItemClickListener import android.widget.{Switch, CheckedTextView}
import android.widget._
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.helper.TaskerSettings import com.github.shadowsocks.helper.TaskerSettings
import com.twofortyfouram.locale.api.{Intent => ApiIntent}
/** /**
* @author CzBiX * @author CzBiX
*/ */
class TaskerActivity extends AppCompatActivity { class TaskerActivity extends AppCompatActivity {
var taskerOption: TaskerSettings = _ private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
var actionSelected: Boolean = false {
val typedArray = obtainStyledAttributes(Array(android.R.attr.selectableItemBackground))
override def onCreate(savedInstanceState: Bundle) { view.setBackgroundResource(typedArray.getResourceId(0, 0))
super.onCreate(savedInstanceState) typedArray.recycle
setContentView(R.layout.layout_tasker)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.app_name)
toolbar.setNavigationIcon(R.drawable.ic_close)
toolbar.setNavigationOnClickListener(_ => finish())
toolbar.inflateMenu(R.menu.tasker_menu)
toolbar.setOnMenuItemClickListener(_.getItemId match {
case R.id.save =>
saveResult()
true
case _ => false
})
loadSettings()
loadFragment()
}
private def loadSettings() {
val intent: Intent = getIntent
if (intent.getAction != ApiIntent.ACTION_EDIT_SETTING) {
Log.w(Shadowsocks.TAG, "unknown tasker action")
finish()
return
}
taskerOption = TaskerSettings.fromIntent(intent)
}
private def loadFragment() {
actionSelected = !taskerOption.isEmpty
val fragment: Fragment = taskerOption.action match {
case TaskerSettings.ACTION_TOGGLE_SERVICE =>
new ToggleServiceFragment
case TaskerSettings.ACTION_SWITCH_PROFILE =>
new ProfileChoiceFragment
case _ =>
val fragment: ActionChoiceFragment = new ActionChoiceFragment
fragment.onItemClickedListener = (_, _, _, id) => {
taskerOption.action = getResources.getStringArray(R.array.tasker_action_value)(id.toInt)
loadFragment()
actionSelected = true
}
fragment
}
getSupportFragmentManager.beginTransaction()
.replace(R.id.fragment, fragment)
.commit()
}
private def saveResult() {
if (actionSelected) {
val fragment: OptionFragment = getSupportFragmentManager.findFragmentById(R.id.fragment).asInstanceOf[OptionFragment]
if (fragment.saveResult()) {
setResult(Activity.RESULT_OK, taskerOption.toIntent(this))
finish()
}
} else {
Toast.makeText(this, R.string.no_action_selected, Toast.LENGTH_SHORT).show()
}
}
private class ActionChoiceFragment extends ListFragment {
var onItemClickedListener: OnItemClickListener = _
override def onActivityCreated(savedInstanceState: Bundle) {
super.onActivityCreated(savedInstanceState)
val adapter = ArrayAdapter.createFromResource(getActivity, R.array.tasker_action_name, android.R.layout.simple_selectable_list_item)
setListAdapter(adapter)
}
override def onListItemClick(l: ListView, v: View, position: Int, id: Long) {
if (onItemClickedListener != null) {
onItemClickedListener.onItemClick(l, v, position, id)
}
}
}
private class ToggleServiceFragment extends Fragment with OptionFragment {
var switch: Switch = _
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = {
val view: View = inflater.inflate(R.layout.layout_tasker_toggle_service, container, false)
switch = view.findViewById(R.id.service_switch).asInstanceOf[Switch]
view
} }
private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
itemView.setOnClickListener(this)
override def onActivityCreated(savedInstanceState: Bundle) { def bindDefault {
super.onActivityCreated(savedInstanceState) item = null
text.setText(R.string.profile_default)
if (!taskerOption.isEmpty) { text.setChecked(taskerOption.profileId < 0)
switch.setChecked(taskerOption.isStart)
} }
def bind(item: Profile) {
this.item = item
text.setText(item.name)
text.setChecked(taskerOption.profileId == item.id)
} }
override def saveResult() = { def onClick(v: View) {
taskerOption.action = TaskerSettings.ACTION_TOGGLE_SERVICE taskerOption.switchOn = switch.isChecked
taskerOption.isStart = switch.isChecked taskerOption.profileId = if (item == null) -1 else item.id
true setResult(Activity.RESULT_OK, taskerOption.toIntent(TaskerActivity.this))
finish
} }
} }
private class ProfileChoiceFragment extends ListFragment with OptionFragment { private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
override def onActivityCreated(savedInstanceState: Bundle) { private val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
super.onActivityCreated(savedInstanceState) def getItemCount = 1 + profiles.length
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match {
import scala.collection.JavaConverters._ case 0 => vh.bindDefault
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty) case _ => vh.bind(profiles(i - 1))
val adapter = new ArrayAdapter[Profile](getActivity, 0, android.R.id.text1, profiles.asJava) {
override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
val view: TextView = if (convertView == null) {
val inflater = LayoutInflater.from(parent.getContext)
inflater.inflate(android.R.layout.simple_list_item_single_choice, parent, false).asInstanceOf[TextView]
} else {
convertView.asInstanceOf[TextView]
}
view.setText(getItem(position).name)
view
} }
private val name = "select_dialog_singlechoice_" + (if (Build.VERSION.SDK_INT >= 21) "material" else "holo")
def onCreateViewHolder(vg: ViewGroup, i: Int) = new ProfileViewHolder(LayoutInflater.from(vg.getContext)
.inflate(Resources.getSystem.getIdentifier(name, "layout", "android"), vg, false))
} }
setListAdapter(adapter) private var taskerOption: TaskerSettings = _
private var switch: Switch = _
val listView: ListView = getListView private val profilesAdapter = new ProfilesAdapter
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE)
if (!taskerOption.isEmpty) { override def onCreate(savedInstanceState: Bundle) {
val index = profiles.indexWhere(_.id == taskerOption.profileId) super.onCreate(savedInstanceState)
if (index > -1) { setContentView(R.layout.layout_tasker)
listView.setItemChecked(index, true)
}
}
}
override def saveResult() = { val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
taskerOption.action = TaskerSettings.ACTION_SWITCH_PROFILE toolbar.setTitle(R.string.app_name)
val listView = getListView toolbar.setNavigationIcon(R.drawable.ic_close)
val item = listView.getItemAtPosition(listView.getCheckedItemPosition) toolbar.setNavigationOnClickListener(_ => finish())
if (item == null) {
Toast.makeText(getActivity, R.string.no_profile_selected, Toast.LENGTH_SHORT).show()
false
} else {
taskerOption.profileId = item.asInstanceOf[Profile].id
true
}
}
}
trait OptionFragment extends Fragment { taskerOption = TaskerSettings.fromIntent(getIntent)
def saveResult(): Boolean switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch]
switch.setChecked(taskerOption.switchOn)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
profilesList.setLayoutManager(new LinearLayoutManager(this))
profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter)
} }
} }
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.content.{BroadcastReceiver, Context, Intent} import android.content.{BroadcastReceiver, Context, Intent}
import android.util.Log
import com.github.shadowsocks.helper.TaskerSettings import com.github.shadowsocks.helper.TaskerSettings
import com.github.shadowsocks.utils.Utils import com.github.shadowsocks.utils.Utils
...@@ -47,20 +46,15 @@ import com.github.shadowsocks.utils.Utils ...@@ -47,20 +46,15 @@ import com.github.shadowsocks.utils.Utils
* @author CzBiX * @author CzBiX
*/ */
class TaskerReceiver extends BroadcastReceiver { class TaskerReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent): Unit = { override def onReceive(context: Context, intent: Intent) {
val settings: TaskerSettings = TaskerSettings.fromIntent(intent) val settings = TaskerSettings.fromIntent(intent)
val switched = ShadowsocksApplication.profileManager.getProfile(settings.profileId) match {
settings.action match { case Some(p) =>
case TaskerSettings.ACTION_TOGGLE_SERVICE =>
if (settings.isStart) {
Utils.startSsService(context)
} else {
Utils.stopSsService(context) Utils.stopSsService(context)
}
case TaskerSettings.ACTION_SWITCH_PROFILE =>
ShadowsocksApplication.switchProfile(settings.profileId) ShadowsocksApplication.switchProfile(settings.profileId)
case _ => true
Log.e(Shadowsocks.TAG, s"unknown tasker action: ${settings.action}") case _ => false
} }
if (settings.switchOn) Utils.startSsService(context) else if (!switched) Utils.stopSsService(context)
} }
} }
...@@ -40,78 +40,35 @@ package com.github.shadowsocks.helper ...@@ -40,78 +40,35 @@ package com.github.shadowsocks.helper
import android.content.{Context, Intent} import android.content.{Context, Intent}
import android.os.Bundle import android.os.Bundle
import android.util.Log import com.github.shadowsocks.{R, ShadowsocksApplication}
import com.github.shadowsocks.{R, Shadowsocks, ShadowsocksApplication}
import com.twofortyfouram.locale.api.{Intent => ApiIntent} import com.twofortyfouram.locale.api.{Intent => ApiIntent}
object TaskerSettings { object TaskerSettings {
val ACTION_UNKNOWN = "unknown" private val KEY_ACTION = "action"
val ACTION_TOGGLE_SERVICE = "toggle_service" private val KEY_SWITCH_ON = "switch_on"
val ACTION_SWITCH_PROFILE = "switch_profile" private val KEY_PROFILE_ID = "profile_id"
def fromIntent(intent: Intent): TaskerSettings = { def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE))
val bundle: Bundle = if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE)) intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY)
intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY
new TaskerSettings(bundle) def fromBundle(bundle: Bundle) = new TaskerSettings(bundle)
}
def fromBundle(bundle: Bundle): TaskerSettings = {
new TaskerSettings(bundle)
}
} }
class TaskerSettings(bundle: Bundle) { class TaskerSettings(bundle: Bundle) {
import TaskerSettings._ import TaskerSettings._
private val KEY_ACTION = "action" var switchOn = bundle.getBoolean(KEY_SWITCH_ON, true)
private val KEY_IS_START = "is_start" var profileId = bundle.getInt(KEY_PROFILE_ID, -1)
private val KEY_PROFILE_ID = "profile_id"
var action: String = bundle.getString(KEY_ACTION, ACTION_UNKNOWN)
var isStart: Boolean = _
var profileId: Int = _
action match { def toIntent(context: Context) = {
case ACTION_TOGGLE_SERVICE =>
isStart = bundle.getBoolean(KEY_IS_START, true)
case ACTION_SWITCH_PROFILE =>
profileId = bundle.getInt(KEY_PROFILE_ID, -1)
assert(profileId != -1, "profile id was wrong")
case _ =>
Log.w(Shadowsocks.TAG, s"unknown tasker action settings: $action")
}
def isEmpty = action == ACTION_UNKNOWN
def toIntent(context: Context): Intent = {
val bundle = new Bundle() val bundle = new Bundle()
if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false)
action match { if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId)
case ACTION_TOGGLE_SERVICE => new Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
bundle.putString(KEY_ACTION, action) ShadowsocksApplication.profileManager.getProfile(profileId) match {
bundle.putBoolean(KEY_IS_START, isStart) case Some(p) => context.getString(if (switchOn) R.string.start_service else R.string.stop_service, p.name)
case ACTION_SWITCH_PROFILE => case None => context.getString(if (switchOn) R.string.start_service_default else R.string.stop)
bundle.putString(KEY_ACTION, action) })
bundle.putInt(KEY_PROFILE_ID, profileId)
}
val desc: String = action match {
case ACTION_TOGGLE_SERVICE =>
context.getString(R.string.turn_service_state,
context.getString(if (isStart) R.string.state_on else R.string.state_off))
case ACTION_SWITCH_PROFILE =>
val profileName = ShadowsocksApplication.profileManager.getProfile(profileId) match {
case Some(p) => p.name
case None => context.getString(R.string.removed)
}
context.getString(R.string.switch_profile_to, profileName)
}
val intent: Intent = new Intent()
intent.putExtra(ApiIntent.EXTRA_STRING_BLURB, desc)
.putExtra(ApiIntent.EXTRA_BUNDLE, bundle)
} }
} }
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