ConfigListFragment: Use a floating action menu

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Samuel Holland 2017-11-28 20:14:47 -06:00
parent 19f0089559
commit 4a672fc05d
8 changed files with 137 additions and 72 deletions

View File

@ -22,5 +22,6 @@ android {
}
dependencies {
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -9,7 +9,6 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import com.wireguard.android.backends.VpnService;
import com.wireguard.config.Config;
/**
@ -18,7 +17,6 @@ import com.wireguard.config.Config;
public class ConfigActivity extends BaseConfigActivity {
private static final String KEY_EDITOR_STATE = "editorState";
private static final int REQUEST_IMPORT = 1;
private static final String TAG_DETAIL = "detail";
private static final String TAG_EDIT = "edit";
private static final String TAG_LIST = "list";
@ -133,18 +131,11 @@ public class ConfigActivity extends BaseConfigActivity {
}
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == REQUEST_IMPORT) {
if (resultCode == RESULT_OK)
VpnService.getInstance().importFrom(data.getData());
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onBackPressed() {
final ConfigListFragment listFragment = (ConfigListFragment) fragments.get(TAG_LIST);
if (listFragment.isVisible() && listFragment.tryCollapseMenu())
return;
super.onBackPressed();
// The visible fragment is now the one that was on top of the back stack, if there was one.
if (isEditing())
@ -200,19 +191,10 @@ public class ConfigActivity extends BaseConfigActivity {
// The back arrow in the action bar should act the same as the back button.
onBackPressed();
return true;
case R.id.menu_action_add:
startActivity(new Intent(this, AddActivity.class));
return true;
case R.id.menu_action_edit:
// Try to make the editing fragment visible.
setIsEditing(true);
return true;
case R.id.menu_action_import:
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_IMPORT);
return true;
case R.id.menu_action_save:
// This menu item is handled by the editing fragment.
return false;

View File

@ -1,12 +1,15 @@
package com.wireguard.android;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
@ -26,28 +29,49 @@ import java.util.List;
*/
public class ConfigListFragment extends BaseConfigFragment {
private ListView listView;
private static final int REQUEST_IMPORT = 1;
private ConfigListFragmentBinding binding;
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == REQUEST_IMPORT) {
if (resultCode == Activity.RESULT_OK)
VpnService.getInstance().importFrom(data.getData());
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.config_list, menu);
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup parent,
final Bundle savedInstanceState) {
final ConfigListFragmentBinding binding =
ConfigListFragmentBinding.inflate(inflater, parent, false);
binding = ConfigListFragmentBinding.inflate(inflater, parent, false);
binding.setConfigs(VpnService.getInstance().getConfigs());
listView = binding.configList;
listView.setMultiChoiceModeListener(new ConfigListModeListener());
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
binding.addFromFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_IMPORT);
binding.addMenu.collapse();
}
});
binding.addFromScratch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
startActivity(new Intent(getActivity(), AddActivity.class));
binding.addMenu.collapse();
}
});
binding.configList.setMultiChoiceModeListener(new ConfigListModeListener());
binding.configList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
@ -55,16 +79,29 @@ public class ConfigListFragment extends BaseConfigFragment {
setCurrentConfig(config);
}
});
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
binding.configList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
setConfigChecked(null);
listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setItemChecked(position, true);
binding.configList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
binding.configList.setItemChecked(position, true);
return true;
}
});
binding.configList.setOnTouchListener(new View.OnTouchListener() {
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(final View view, final MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN &&
binding.configList.pointToPosition((int) event.getX(), (int) event.getY())
== ListView.INVALID_POSITION) {
binding.addMenu.collapse();
return true;
}
return false;
}
});
binding.executePendingBindings();
setConfigChecked(getCurrentConfig());
return binding.getRoot();
@ -75,31 +112,38 @@ public class ConfigListFragment extends BaseConfigFragment {
final BaseConfigActivity activity = ((BaseConfigActivity) getActivity());
if (activity != null)
activity.setCurrentConfig(config);
if (listView != null)
if (binding != null)
setConfigChecked(config);
}
@Override
public void onDestroyView() {
super.onDestroyView();
listView = null;
binding = null;
}
private void setConfigChecked(final Config config) {
if (config != null) {
@SuppressWarnings("unchecked")
final ObservableMapAdapter<String, Config> adapter =
(ObservableMapAdapter<String, Config>) listView.getAdapter();
@SuppressWarnings("unchecked") final ObservableMapAdapter<String, Config> adapter =
(ObservableMapAdapter<String, Config>) binding.configList.getAdapter();
final int position = adapter.getPosition(config.getName());
if (position >= 0)
listView.setItemChecked(position, true);
binding.configList.setItemChecked(position, true);
} else {
final int position = listView.getCheckedItemPosition();
final int position = binding.configList.getCheckedItemPosition();
if (position >= 0)
listView.setItemChecked(position, false);
binding.configList.setItemChecked(position, false);
}
}
public boolean tryCollapseMenu() {
if (binding != null && binding.addMenu.isExpanded()) {
binding.addMenu.collapse();
return true;
}
return false;
}
private class ConfigListModeListener implements AbsListView.MultiChoiceModeListener {
private final List<Config> configsToRemove = new LinkedList<>();
@ -124,11 +168,11 @@ public class ConfigListFragment extends BaseConfigFragment {
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
final long id, final boolean checked) {
if (checked)
configsToRemove.add((Config) listView.getItemAtPosition(position));
configsToRemove.add((Config) binding.configList.getItemAtPosition(position));
else
configsToRemove.remove(listView.getItemAtPosition(position));
configsToRemove.remove(binding.configList.getItemAtPosition(position));
final int count = configsToRemove.size();
final Resources resources = listView.getContext().getResources();
final Resources resources = binding.getRoot().getContext().getResources();
mode.setTitle(resources.getQuantityString(R.plurals.list_delete_title, count, count));
}
@ -141,10 +185,10 @@ public class ConfigListFragment extends BaseConfigFragment {
@Override
public void onDestroyActionMode(final ActionMode mode) {
configsToRemove.clear();
listView.post(new Runnable() {
binding.configList.post(new Runnable() {
@Override
public void run() {
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
binding.configList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
// Restore the previous selection (before entering the action mode).
setConfigChecked(getCurrentConfig());
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="4dp" />
<padding
android:bottom="4dp"
android:left="8dp"
android:right="8dp"
android:top="4dp" />
<solid android:color="?android:attr/colorPrimary" />
</shape>

View File

@ -10,11 +10,45 @@
type="com.wireguard.android.bindings.ObservableSortedMap&lt;String, com.wireguard.config.Config&gt;" />
</data>
<ListView
android:id="@+id/config_list"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
app:items="@{configs}"
app:layout="@{@layout/config_list_item}" />
android:layout_height="match_parent">
<ListView
android:id="@+id/config_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
app:items="@{configs}"
app:layout="@{@layout/config_list_item}" />
<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/add_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:fab_labelStyle="@style/fab_label"
app:fab_labelsPosition="left">
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/add_from_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_icon="@drawable/ic_action_save"
app:fab_size="mini"
app:fab_title="@string/add_from_file" />
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/add_from_scratch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_icon="@drawable/ic_action_edit"
app:fab_size="mini"
app:fab_title="@string/add_from_scratch" />
</com.getbase.floatingactionbutton.FloatingActionsMenu>
</RelativeLayout>
</layout>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_action_add"
android:alphabeticShortcut="n"
android:icon="@drawable/ic_action_add"
android:showAsAction="always"
android:title="@string/add" />
<item
android:id="@+id/menu_action_import"
android:alphabeticShortcut="o"
android:icon="@drawable/ic_action_open"
android:showAsAction="ifRoom"
android:title="@string/import_config" />
</menu>

View File

@ -4,8 +4,9 @@
<item quantity="one">%d configuration selected</item>
<item quantity="other">%d configurations selected</item>
</plurals>
<string name="add">Add empty config</string>
<string name="add_activity_title">New WireGuard configuration</string>
<string name="add_from_file">Add from file</string>
<string name="add_from_scratch">Add from scratch</string>
<string name="add_peer">Add peer</string>
<string name="addresses">Addresses</string>
<string name="allowed_ips">Allowed IPs</string>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="fab_label" parent="android:TextAppearance.DeviceDefault.Inverse">
<item name="android:background">@drawable/fab_label_background</item>
</style>
</resources>