diff --git a/app/src/main/java/com/wireguard/android/activity/MainActivity.java b/app/src/main/java/com/wireguard/android/activity/MainActivity.java index b3c21133..2d524758 100644 --- a/app/src/main/java/com/wireguard/android/activity/MainActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/MainActivity.java @@ -10,12 +10,13 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.util.Log; +import android.support.v7.app.ActionBar; import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; import com.wireguard.android.R; 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.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 * WireGuard application, and contains several fragments for listing, viewing details of, and * editing the configuration and interface state of WireGuard tunnels. */ -public class MainActivity extends BaseActivity { - private static final String KEY_STATE = "fragment_state"; - private static final String TAG = "WireGuard/" + MainActivity.class.getSimpleName(); - - private State state = State.EMPTY; - - 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; - } +public class MainActivity extends BaseActivity + implements FragmentManager.OnBackStackChangedListener { + @Nullable private ActionBar actionBar; + private boolean isTwoPaneLayout; + @Nullable private TunnelListFragment listFragment; @Override public void onBackPressed() { - final List fragments = getSupportFragmentManager().getFragments(); - - boolean handled = false; - if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) { - handled = ((TunnelListFragment) fragments.get(0)).collapseActionMenu(); + final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount(); + // If the action menu is visible and expanded, collapse it instead of navigating back. + if (isTwoPaneLayout || backStackEntries == 0) { + if (listFragment != null && listFragment.collapseActionMenu()) + return; } - - if (!handled) { - handled = moveToState(State.ofLayer(state.layer - 1)); + // If the two-pane layout does not have an editor open, going back should exit the app. + if (isTwoPaneLayout && backStackEntries <= 1) { + finish(); + return; } - - if (!handled) { - super.onBackPressed(); + // Deselect the current tunnel on navigating back from the detail pane to the one-pane list. + if (!isTwoPaneLayout && backStackEntries == 1) { + setSelectedTunnel(null); + return; } + 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 @@ -97,25 +73,14 @@ public class MainActivity extends BaseActivity { protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); - if (savedInstanceState != null && savedInstanceState.getString(KEY_STATE) != null) - state = State.valueOf(savedInstanceState.getString(KEY_STATE)); - if (state == State.EMPTY) { - State initialState = getSelectedTunnel() != null ? State.DETAIL : State.LIST; - if (getIntent() != null && getIntent().getStringExtra(KEY_STATE) != null) - initialState = State.valueOf(getIntent().getStringExtra(KEY_STATE)); - moveToState(initialState); - } - updateActionBar(); - final int actionBarId = getResources().getIdentifier("action_bar", "id", getPackageName()); - if (actionBarId != 0 && findViewById(actionBarId) != null) { - findViewById(actionBarId).setOnTouchListener((v, e) -> { - final List fragments = getSupportFragmentManager().getFragments(); - if (!fragments.isEmpty() && fragments.get(0) instanceof TunnelListFragment) { - ((TunnelListFragment) fragments.get(0)).collapseActionMenu(); - } - return false; - }); - } + actionBar = getSupportActionBar(); + isTwoPaneLayout = findViewById(R.id.master_detail_wrapper) instanceof LinearLayout; + listFragment = (TunnelListFragment) getSupportFragmentManager().findFragmentByTag("LIST"); + getSupportFragmentManager().addOnBackStackChangedListener(this); + onBackStackChanged(); + final View actionBarView = findViewById(R.id.action_bar); + if (actionBarView != null) + actionBarView.setOnTouchListener((v, e) -> listFragment != null && listFragment.collapseActionMenu()); } @Override @@ -129,11 +94,14 @@ public class MainActivity extends BaseActivity { switch (item.getItemId()) { case android.R.id.home: // The back arrow in the action bar should act the same as the back button. - moveToState(State.ofLayer(state.layer - 1)); + onBackPressed(); return true; case R.id.menu_action_edit: - if (getSelectedTunnel() != null) - moveToState(State.EDITOR); + getSupportFragmentManager().beginTransaction() + .replace(R.id.detail_container, new TunnelEditorFragment()) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .addToBackStack(null) + .commit(); return true; case R.id.menu_action_save: // This menu item is handled by the editor fragment. @@ -147,37 +115,26 @@ public class MainActivity extends BaseActivity { } @Override - protected void onSaveInstanceState(final Bundle outState) { - outState.putString(KEY_STATE, state.name()); - super.onSaveInstanceState(outState); - } - - @Override - protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { - moveToState(newTunnel != null ? State.DETAIL : State.LIST); - } - - private void updateActionBar() { - if (getSupportActionBar() != null) - getSupportActionBar().setDisplayHomeAsUpEnabled(state.layer > State.LIST.layer); - } - - 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 fragment, final int layer) { - this.fragment = fragment != null ? fragment.getName() : null; - this.layer = layer; + protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, + @Nullable final Tunnel newTunnel) { + 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; } - - private static State ofLayer(final int layer) { - return Stream.of(State.values()).filter(s -> s.layer == layer).findFirst().get(); + if (backStackEntries == 2) { + // Pop the editor off the back stack to reveal the detail fragment. Use the immediate + // method to avoid the editor picking up the new tunnel while it is still visible. + fragmentManager.popBackStackImmediate(); + } else if (backStackEntries == 0) { + // Create and show a new detail fragment. + fragmentManager.beginTransaction() + .add(R.id.detail_container, new TunnelDetailFragment()) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .addToBackStack(null) + .commit(); } } } diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java index 1ab4f2a2..0d432a20 100644 --- a/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java +++ b/app/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.java @@ -174,6 +174,7 @@ public class TunnelEditorFragment extends BaseFragment implements AppExclusionLi } // Tell the activity to finish itself or go back to the detail view. 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. final Tunnel savedTunnel = tunnel; if (savedTunnel == getSelectedTunnel()) diff --git a/app/src/main/res/layout-w600dp/main_activity.xml b/app/src/main/res/layout-w600dp/main_activity.xml new file mode 100644 index 00000000..b751c8c1 --- /dev/null +++ b/app/src/main/res/layout-w600dp/main_activity.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml index 0f21e2e8..a4fd7fd1 100644 --- a/app/src/main/res/layout/main_activity.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -1,7 +1,19 @@ + tools:context=".activity.MainActivity"> + + + + +