/*
* Copyright 2013 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.mortar.core;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.FrameLayout;
import com.example.mortar.R;
import com.example.mortar.android.ActionBarOwner;
import com.example.mortar.nodes.NodeClearManager;
import com.example.mortar.nodes.NodeCreationManager;
import com.example.mortar.nodes.NodeStateManager;
import com.example.mortar.nodes.TreeNodes;
import com.example.mortar.screen.ChatListScreen;
import com.example.mortar.screen.FriendListScreen;
import com.example.mortar.util.BackSupport;
import com.example.mortar.util.DaggerService;
import com.example.mortar.util.NodePrinter;
import com.zhuinden.servicetree.ServiceTree;
import com.zhuinden.simplestack.HistoryBuilder;
import com.zhuinden.simplestack.KeyParceler;
import com.zhuinden.simplestack.StateChange;
import com.zhuinden.simplestack.StateChanger;
import com.zhuinden.simplestack.navigator.DefaultStateChanger;
import com.zhuinden.simplestack.navigator.Navigator;
import com.zhuinden.simplestack.navigator.StateKey;
import com.zhuinden.statebundle.StateBundle;
import javax.inject.Inject;
import io.reactivex.functions.Action;
import static android.view.MenuItem.SHOW_AS_ACTION_ALWAYS;
/**
* A well intentioned but overly complex sample that demonstrates
* the use of Mortar, Flow and Dagger in a single app.
*/
public class MortarDemoActivity
extends AppCompatActivity
implements ActionBarOwner.Activity, StateChanger {
private static boolean DID_RESTORE_AFTER_PROCESS_DEATH = false;
private ServiceTree.Node activityScope;
private ActionBarOwner.MenuAction actionBarMenuAction;
@Inject
ActionBarOwner actionBarOwner;
@Inject
KeyParceler keyParceler;
@Inject
ServiceTree serviceTree;
@Inject
NodeStateManager nodeStateManager;
private FrameLayout container;
@Override
public Context getContext() {
return this;
}
@Override
public void handleStateChange(StateChange stateChange, Callback completionCallback) {
StateKey newScreen = stateChange.topNewState();
String title = newScreen.getClass().getSimpleName();
ActionBarOwner.MenuAction menu = new ActionBarOwner.MenuAction("Friends", new Action() {
@Override
public void run()
throws Exception {
Navigator.getBackstack(MortarDemoActivity.this).goTo(new FriendListScreen());
}
});
actionBarOwner.setConfig(new ActionBarOwner.Config(false, !(newScreen instanceof ChatListScreen), title, menu));
completionCallback.stateChangeComplete();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
DID_RESTORE_AFTER_PROCESS_DEATH = true; // no need to restore if no process death could have occurred
}
if(savedInstanceState != null && !DID_RESTORE_AFTER_PROCESS_DEATH) {
DID_RESTORE_AFTER_PROCESS_DEATH = true;
StateBundle rootBundle = savedInstanceState.getParcelable(NodeStateManager.SERVICE_STATES);
if(rootBundle != null) { // global service state is restored after process death
SingletonComponent singletonComponent = DaggerService.get(this); // not yet injected by field injection
ServiceTree _serviceTree = singletonComponent.serviceTree();
NodeStateManager _nodeStateManager = singletonComponent.nodeStateManager();
_serviceTree.registerRootService(NodeStateManager.SERVICE_STATES, rootBundle);
_nodeStateManager.restoreStatesForNode(_serviceTree.getTreeRoot()); // root services should be restored after process death
}
}
ServiceTree.Node parentScope = TreeNodes.getNode(getApplication());
String scopeName = getLocalClassName() + "-task-" + getTaskId();
if(!parentScope.hasChild(scopeName)) {
parentScope.createChild(scopeName);
}
activityScope = parentScope.getChild(scopeName);
DaggerService.<SingletonComponent>get(this).inject(this);
actionBarOwner.takeView(this);
setContentView(R.layout.root_layout);
container = (FrameLayout) findViewById(R.id.container);
Navigator.configure()
.setKeyParceler(keyParceler)
.addStateChangeCompletionListener(new NodeClearManager(serviceTree,
nodeStateManager)) // to delete un-used mortar scopes
.setStateChanger(DefaultStateChanger.configure()
.setExternalStateChanger(this)
.setContextCreationStrategy(new NodeCreationManager(serviceTree,
activityScope,
nodeStateManager))
.create(this, container))
.install(this, container, HistoryBuilder.single(new ChatListScreen()));
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(NodeStateManager.SERVICE_STATES, nodeStateManager.persistStates());
}
@Override
public Object getSystemService(String name) {
if(TreeNodes.NODE_TAG.equals(name)) {
return activityScope;
}
if(activityScope != null && activityScope.hasService(name)) {
return activityScope.getService(name);
}
return super.getSystemService(name);
}
/**
* Inform the view about back events.
*/
@Override
public void onBackPressed() {
if(!BackSupport.onBackPressed(container.getChildAt(0))) {
super.onBackPressed();
}
}
/**
* Inform the view about up events.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == android.R.id.home) {
boolean handled = BackSupport.onBackPressed(container.getChildAt(0));
if(handled) {
return true;
}
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Configure the action bar menu as required by {@link ActionBarOwner.Activity}.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if(actionBarMenuAction != null) {
menu.add(actionBarMenuAction.title)
.setShowAsActionFlags(SHOW_AS_ACTION_ALWAYS)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
try {
actionBarMenuAction.action.run();
} catch(Exception e) {
throw new RuntimeException(e);
}
return true;
}
});
}
menu.add("Log Scope Hierarchy").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Log.i("MORTAR", NodePrinter.scopeHierarchyToString(activityScope));
return true;
}
});
return true;
}
@Override
protected void onDestroy() {
actionBarOwner.dropView(this);
actionBarOwner.setConfig(null);
// activityScope may be null in case isWrongInstance() returned true in onCreate()
if(isFinishing() && activityScope != null) {
activityScope.removeNodeAndChildren();
activityScope = null;
}
super.onDestroy();
}
@Override
public void setShowHomeEnabled(boolean enabled) {
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowHomeEnabled(false);
}
@Override
public void setUpButtonEnabled(boolean enabled) {
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(enabled);
actionBar.setHomeButtonEnabled(enabled);
}
@Override
public void setTitle(CharSequence title) {
getSupportActionBar().setTitle(title);
}
@Override
public void setMenu(ActionBarOwner.MenuAction action) {
if(action != actionBarMenuAction) {
actionBarMenuAction = action;
invalidateOptionsMenu();
}
}
}