aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/net/evendanan/pushingpixels/FragmentChauffeurActivity.java
blob: 175f6149517e7c0eb55dd6cb56ff2eac35af64a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
 * Copyright (c) 2013 Menny Even-Danan
 *
 * 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 net.evendanan.pushingpixels;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.support.v7.app.ActionBarActivity;
import android.view.View;

import com.anysoftkeyboard.utils.Log;
import com.menny.android.anysoftkeyboard.R;

public abstract class FragmentChauffeurActivity extends ActionBarActivity {

    public static enum FragmentUiContext {
        RootFragment,
        DeeperExperience,
        ExpandedItem,
        IncomingAlert
    }

    private static final String TAG = "chauffeur";

    private static final String ROOT_FRAGMENT_TAG = "FragmentChauffeurActivity_ROOT_FRAGMENT_TAG";

    private static final String KEY_FRAGMENT_CLASS_TO_ADD = "KEY_FRAGMENT_CLASS_TO_ADD";
    private static final String KEY_FRAGMENT_ARGS_TO_ADD = "KEY_FRAGMENT_ARGS_TO_ADD";

    public static void addIntentArgsForAddingFragmentToUi(@NonNull Intent intent, @NonNull Class<? extends Fragment> fragmentClass, @Nullable Bundle fragmentArgs) {
        intent.putExtra(KEY_FRAGMENT_CLASS_TO_ADD, fragmentClass);
        if (fragmentArgs != null)
            intent.putExtra(KEY_FRAGMENT_ARGS_TO_ADD, fragmentArgs);
    }

	private boolean mIsActivityShown = false;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
	    mIsActivityShown = true;
        if (savedInstanceState == null) {
            //setting up the root of the UI.
            setRootFragment(createRootFragmentInstance());
            //now, checking if there is a request to add a fragment on-top of this one.
            Bundle activityArgs = getIntent().getExtras();
            if (activityArgs != null && activityArgs.containsKey(KEY_FRAGMENT_CLASS_TO_ADD)) {
                Class<? extends Fragment> fragmentClass = (Class<? extends Fragment>) activityArgs.get(KEY_FRAGMENT_CLASS_TO_ADD);
                //not sure that this is a best-practice, but I still need to remove this from the activity's args
                activityArgs.remove(KEY_FRAGMENT_CLASS_TO_ADD);
                try {
                    Fragment fragment = fragmentClass.newInstance();
                    if (activityArgs.containsKey(KEY_FRAGMENT_ARGS_TO_ADD)) {
                        fragment.setArguments(activityArgs.getBundle(KEY_FRAGMENT_ARGS_TO_ADD));
                        activityArgs.remove(KEY_FRAGMENT_CLASS_TO_ADD);
                    }
                    addFragmentToUi(fragment, FragmentUiContext.RootFragment);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    protected abstract int getFragmentRootUiElementId();

    protected abstract Fragment createRootFragmentInstance();

    public void returnToRootFragment() {
	    if (!mIsActivityShown) return;

	    getSupportFragmentManager().popBackStackImmediate(ROOT_FRAGMENT_TAG, 0 /*don't pop the root*/);
    }

    public void setRootFragment(Fragment fragment) {
        getSupportFragmentManager().popBackStack(ROOT_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.setCustomAnimations(R.anim.ui_context_root_add_in, R.anim.ui_context_root_add_out,
                R.anim.ui_context_root_pop_in, R.anim.ui_context_root_pop_out);
        transaction.replace(getFragmentRootUiElementId(), fragment);
        //bookmarking, so I can return easily.
        transaction.addToBackStack(ROOT_FRAGMENT_TAG);
        transaction.commit();
    }

    public void addFragmentToUi(@NonNull Fragment fragment, FragmentUiContext experience) {
        addFragmentToUi(fragment, experience, null);
    }

    /**
     * Adds the given fragment into the UI using the specified UI-context animation.
     *
     * @param fragment      any generic Fragment. For the ExpandedItem animation it is best to use a PassengerFragment
     * @param experience
     * @param originateView a hint view which will be used to fine-tune the ExpandedItem animation
     */
    public void addFragmentToUi(@NonNull Fragment fragment, FragmentUiContext experience, @Nullable View originateView) {
	    if (!mIsActivityShown) return;

        if (experience == FragmentUiContext.RootFragment) {
            //in this case, I need to pop all the other fragments till the root.
            returnToRootFragment();
        }
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        //note: the animation should be declared before the fragment replace call, so the transaction will know to which fragment change it should be associated with.
        switch (experience) {
            case RootFragment:
                transaction.setCustomAnimations(R.anim.ui_context_root_add_in, R.anim.ui_context_root_add_out,
                        R.anim.ui_context_root_pop_in, R.anim.ui_context_root_pop_out);
                break;
            case DeeperExperience:
                transaction.setCustomAnimations(R.anim.ui_context_deeper_add_in, R.anim.ui_context_deeper_add_out,
                        R.anim.ui_context_deeper_pop_in, R.anim.ui_context_deeper_pop_out);
                break;
            case ExpandedItem:
                //although this managing Activity can handle any generic Fragment, in this case we'll need some help from the fragment.
                //it is required to fine tune the pivot of the scale animation.
                //so, I'll need the specialized fragment PassengerFragment
                if (fragment instanceof Passengerable && originateView != null) {
                    View fragmentParent = findViewById(getFragmentRootUiElementId());

                    // Idea taken from:
                    // http://developer.android.com/training/animation/zoom.html
                    final float scaleX = ((float) originateView.getWidth())
                            / ((float) fragmentParent.getWidth());
                    final float scaleY = ((float) originateView.getHeight())
                            / ((float) fragmentParent.getHeight());
                    // some preparations
                    // the Y pivot is tricky, it should be the middle of the button, but in
                    // the fragmentParent coordinates
                    int[] originateLocation = new int[2];
                    originateView.getLocationInWindow(originateLocation);
                    int[] parentLocation = new int[2];
                    fragmentParent.getLocationInWindow(parentLocation);
                    final int pivotY = originateLocation[1] - parentLocation[1] + (originateView.getHeight() / 2);
                    final int pivotX = originateLocation[0] - parentLocation[0] + (originateView.getWidth() / 2);

                    Passengerable passengerFragment = (Passengerable) fragment;
                    passengerFragment.setItemExpandExtraData(pivotX, pivotY, scaleX, scaleY);
                    transaction.setCustomAnimations(R.anim.ui_context_expand_add_in, R.anim.ui_context_expand_add_out,
                            R.anim.ui_context_expand_pop_in, R.anim.ui_context_expand_pop_out);
                } else {
                    //using the default scale animation, no pivot changes can be done on a generic fragment.
                    transaction.setCustomAnimations(R.anim.ui_context_expand_add_in_default, R.anim.ui_context_expand_add_out,
                            R.anim.ui_context_expand_pop_in, R.anim.ui_context_expand_pop_out_default);
                }
                break;
            case IncomingAlert:
                transaction.setCustomAnimations(R.anim.ui_context_dialog_add_in, R.anim.ui_context_dialog_add_out,
                        R.anim.ui_context_dialog_pop_in, R.anim.ui_context_dialog_pop_out);
                break;
            default:
                Log.wtf(TAG, "I don't know what is this UI experience type: " + experience);
                break;
        }
        //these two calls will make sure the back-button will switch to previous fragment
        transaction.replace(getFragmentRootUiElementId(), fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
            //the UI is empty. I can safely finish the activity
            finish();
        }
    }

	@Override
	protected void onStart() {
		super.onStart();
		mIsActivityShown = true;
	}

	@Override
	protected void onStop() {
		super.onStop();
		mIsActivityShown = false;
	}

	public final boolean isChaufferActivityVisible() {
		return mIsActivityShown;
	}
}