MainActivity: Fix fragment selection logic
Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
parent
e29c21f8df
commit
ca92ac60b7
@ -10,12 +10,13 @@ import android.annotation.SuppressLint;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.util.Log;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.android.fragment.TunnelDetailFragment;
|
import com.wireguard.android.fragment.TunnelDetailFragment;
|
||||||
@ -23,71 +24,46 @@ import com.wireguard.android.fragment.TunnelEditorFragment;
|
|||||||
import com.wireguard.android.fragment.TunnelListFragment;
|
import com.wireguard.android.fragment.TunnelListFragment;
|
||||||
import com.wireguard.android.model.Tunnel;
|
import com.wireguard.android.model.Tunnel;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import java9.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
* CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
|
||||||
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
* WireGuard application, and contains several fragments for listing, viewing details of, and
|
||||||
* editing the configuration and interface state of WireGuard tunnels.
|
* editing the configuration and interface state of WireGuard tunnels.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class MainActivity extends BaseActivity {
|
public class MainActivity extends BaseActivity
|
||||||
private static final String KEY_STATE = "fragment_state";
|
implements FragmentManager.OnBackStackChangedListener {
|
||||||
private static final String TAG = "WireGuard/" + MainActivity.class.getSimpleName();
|
@Nullable private ActionBar actionBar;
|
||||||
|
private boolean isTwoPaneLayout;
|
||||||
private State state = State.EMPTY;
|
@Nullable private TunnelListFragment listFragment;
|
||||||
|
|
||||||
private boolean moveToState(final State nextState) {
|
|
||||||
if (state == nextState)
|
|
||||||
return false;
|
|
||||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
Log.i(TAG, "Moving from " + state.name() + " to " + nextState.name());
|
|
||||||
if (nextState.layer > state.layer + 1) {
|
|
||||||
moveToState(State.ofLayer(state.layer + 1));
|
|
||||||
moveToState(nextState);
|
|
||||||
return true;
|
|
||||||
} else if (nextState.layer == state.layer + 1) {
|
|
||||||
final Fragment fragment = Fragment.instantiate(this, nextState.fragment);
|
|
||||||
final FragmentTransaction transaction = fragmentManager.beginTransaction()
|
|
||||||
.replace(R.id.master_fragment, fragment)
|
|
||||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
|
||||||
if (state.layer > 0)
|
|
||||||
transaction.addToBackStack(null);
|
|
||||||
transaction.commitAllowingStateLoss(); /* TODO: switch back to .commit() when this function is rewritten. */
|
|
||||||
} else if (nextState.layer == state.layer - 1) {
|
|
||||||
if (fragmentManager.getBackStackEntryCount() == 0)
|
|
||||||
return false;
|
|
||||||
fragmentManager.popBackStack();
|
|
||||||
} else if (nextState.layer < state.layer - 1) {
|
|
||||||
moveToState(State.ofLayer(state.layer - 1));
|
|
||||||
moveToState(nextState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
state = nextState;
|
|
||||||
if (state.layer <= State.LIST.layer)
|
|
||||||
setSelectedTunnel(null);
|
|
||||||
updateActionBar();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
|
final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
|
||||||
|
// If the action menu is visible and expanded, collapse it instead of navigating back.
|
||||||
boolean handled = false;
|
if (isTwoPaneLayout || backStackEntries == 0) {
|
||||||
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
|
if (listFragment != null && listFragment.collapseActionMenu())
|
||||||
handled = ((TunnelListFragment) fragments.get(0)).collapseActionMenu();
|
return;
|
||||||
}
|
}
|
||||||
|
// If the two-pane layout does not have an editor open, going back should exit the app.
|
||||||
if (!handled) {
|
if (isTwoPaneLayout && backStackEntries <= 1) {
|
||||||
handled = moveToState(State.ofLayer(state.layer - 1));
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Deselect the current tunnel on navigating back from the detail pane to the one-pane list.
|
||||||
|
if (!isTwoPaneLayout && backStackEntries == 1) {
|
||||||
|
setSelectedTunnel(null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) {
|
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void onBackStackChanged() {
|
||||||
|
if (actionBar == null)
|
||||||
|
return;
|
||||||
|
// Do not show the home menu when the two-pane layout is at the detail view (see above).
|
||||||
|
final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
|
||||||
|
final int minBackStackEntries = isTwoPaneLayout ? 2 : 1;
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(backStackEntries >= minBackStackEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use onTouchListener here to avoid the UI click sound, hence
|
// We use onTouchListener here to avoid the UI click sound, hence
|
||||||
@ -97,25 +73,14 @@ public class MainActivity extends BaseActivity {
|
|||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null)
|
actionBar = getSupportActionBar();
|
||||||
state = State.valueOf(savedInstanceState.getString(KEY_STATE));
|
isTwoPaneLayout = findViewById(R.id.master_detail_wrapper) instanceof LinearLayout;
|
||||||
if (state == State.EMPTY) {
|
listFragment = (TunnelListFragment) getSupportFragmentManager().findFragmentByTag("LIST");
|
||||||
State initialState = getSelectedTunnel() != null ? State.DETAIL : State.LIST;
|
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||||
if (getIntent() != null && getIntent().getStringExtra(KEY_STATE) != null)
|
onBackStackChanged();
|
||||||
initialState = State.valueOf(getIntent().getStringExtra(KEY_STATE));
|
final View actionBarView = findViewById(R.id.action_bar);
|
||||||
moveToState(initialState);
|
if (actionBarView != null)
|
||||||
}
|
actionBarView.setOnTouchListener((v, e) -> listFragment != null && listFragment.collapseActionMenu());
|
||||||
updateActionBar();
|
|
||||||
final int actionBarId = getResources().getIdentifier("action_bar", "id", getPackageName());
|
|
||||||
if (actionBarId != 0 && findViewById(actionBarId) != null) {
|
|
||||||
findViewById(actionBarId).setOnTouchListener((v, e) -> {
|
|
||||||
final List<Fragment> fragments = getSupportFragmentManager().getFragments();
|
|
||||||
if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) {
|
|
||||||
((TunnelListFragment) fragments.get(0)).collapseActionMenu();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,11 +94,14 @@ public class MainActivity extends BaseActivity {
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
// The back arrow in the action bar should act the same as the back button.
|
// The back arrow in the action bar should act the same as the back button.
|
||||||
moveToState(State.ofLayer(state.layer - 1));
|
onBackPressed();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_action_edit:
|
case R.id.menu_action_edit:
|
||||||
if (getSelectedTunnel() != null)
|
getSupportFragmentManager().beginTransaction()
|
||||||
moveToState(State.EDITOR);
|
.replace(R.id.detail_container, new TunnelEditorFragment())
|
||||||
|
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_action_save:
|
case R.id.menu_action_save:
|
||||||
// This menu item is handled by the editor fragment.
|
// This menu item is handled by the editor fragment.
|
||||||
@ -147,37 +115,26 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(final Bundle outState) {
|
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel,
|
||||||
outState.putString(KEY_STATE, state.name());
|
@Nullable final Tunnel newTunnel) {
|
||||||
super.onSaveInstanceState(outState);
|
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
final int backStackEntries = fragmentManager.getBackStackEntryCount();
|
||||||
|
if (newTunnel == null) {
|
||||||
|
// Clear everything off the back stack (all editors and detail fragments).
|
||||||
|
fragmentManager.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (backStackEntries == 2) {
|
||||||
@Override
|
// Pop the editor off the back stack to reveal the detail fragment. Use the immediate
|
||||||
protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) {
|
// method to avoid the editor picking up the new tunnel while it is still visible.
|
||||||
moveToState(newTunnel != null ? State.DETAIL : State.LIST);
|
fragmentManager.popBackStackImmediate();
|
||||||
}
|
} else if (backStackEntries == 0) {
|
||||||
|
// Create and show a new detail fragment.
|
||||||
private void updateActionBar() {
|
fragmentManager.beginTransaction()
|
||||||
if (getSupportActionBar() != null)
|
.add(R.id.detail_container, new TunnelDetailFragment())
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(state.layer > State.LIST.layer);
|
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||||
}
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
private enum State {
|
|
||||||
EMPTY(null, 0),
|
|
||||||
LIST(TunnelListFragment.class, 1),
|
|
||||||
DETAIL(TunnelDetailFragment.class, 2),
|
|
||||||
EDITOR(TunnelEditorFragment.class, 3);
|
|
||||||
|
|
||||||
@Nullable private final String fragment;
|
|
||||||
private final int layer;
|
|
||||||
|
|
||||||
State(@Nullable final Class<? extends Fragment> fragment, final int layer) {
|
|
||||||
this.fragment = fragment != null ? fragment.getName() : null;
|
|
||||||
this.layer = layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static State ofLayer(final int layer) {
|
|
||||||
return Stream.of(State.values()).filter(s -> s.layer == layer).findFirst().get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi
|
|||||||
}
|
}
|
||||||
// Tell the activity to finish itself or go back to the detail view.
|
// Tell the activity to finish itself or go back to the detail view.
|
||||||
getActivity().runOnUiThread(() -> {
|
getActivity().runOnUiThread(() -> {
|
||||||
|
// TODO(smaeul): Remove this hack when fixing the Config ViewModel
|
||||||
// The selected tunnel has to actually change, but we have to remember this one.
|
// The selected tunnel has to actually change, but we have to remember this one.
|
||||||
final Tunnel savedTunnel = tunnel;
|
final Tunnel savedTunnel = tunnel;
|
||||||
if (savedTunnel == getSelectedTunnel())
|
if (savedTunnel == getSelectedTunnel())
|
||||||
|
25
app/src/main/res/layout-w600dp/main_activity.xml
Normal file
25
app/src/main/res/layout-w600dp/main_activity.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/master_detail_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:divider="?android:attr/dividerHorizontal"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:showDividers="middle"
|
||||||
|
tools:context=".activity.MainActivity">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:name="com.wireguard.android.fragment.TunnelListFragment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:tag="LIST" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/detail_container"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="3" />
|
||||||
|
</LinearLayout>
|
@ -1,7 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/master_fragment"
|
android:id="@+id/master_detail_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:ignore="MergeRootFrame" />
|
tools:context=".activity.MainActivity">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:name="com.wireguard.android.fragment.TunnelListFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:tag="LIST" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/detail_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</FrameLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user