ConfigListFragment: Use a floating action menu
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
19f0089559
commit
4a672fc05d
@ -22,5 +22,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.getbase:floatingactionbutton:1.10.1'
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
10
app/src/main/res/drawable/fab_label_background.xml
Normal file
10
app/src/main/res/drawable/fab_label_background.xml
Normal 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>
|
@ -10,11 +10,45 @@
|
||||
type="com.wireguard.android.bindings.ObservableSortedMap<String, com.wireguard.config.Config>" />
|
||||
</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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
8
app/src/main/res/values/styles.xml
Normal file
8
app/src/main/res/values/styles.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user