ContactDetailLayoutController.java revision 22cb663a251af60bc6beeb1954568c8e6a4c34e9
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.detail;
18
19import com.android.contacts.ContactLoader;
20import com.android.contacts.R;
21import com.android.contacts.activities.PeopleActivity.ContactDetailFragmentListener;
22
23import android.app.FragmentManager;
24import android.app.FragmentTransaction;
25import android.content.Context;
26import android.os.Bundle;
27import android.support.v4.view.ViewPager;
28import android.support.v4.view.ViewPager.OnPageChangeListener;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.widget.AbsListView;
33import android.widget.AbsListView.OnScrollListener;
34
35/**
36 * Determines the layout of the contact card.
37 */
38public class ContactDetailLayoutController {
39
40    public static final int FRAGMENT_COUNT = 2;
41
42    private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
43
44    private enum LayoutMode {
45        TWO_COLUMN, VIEW_PAGER_AND_CAROUSEL,
46    }
47
48    private final LayoutInflater mLayoutInflater;
49    private final FragmentManager mFragmentManager;
50
51    private ContactDetailFragment mDetailFragment;
52    private ContactDetailUpdatesFragment mUpdatesFragment;
53
54    private View mDetailFragmentView;
55    private View mUpdatesFragmentView;
56
57    private final ViewPager mViewPager;
58    private final ContactDetailTabCarousel mTabCarousel;
59    private ContactDetailViewPagerAdapter mViewPagerAdapter;
60
61    private ContactDetailFragmentListener mContactDetailFragmentListener;
62
63    private ContactLoader.Result mContactData;
64
65    private boolean mContactHasUpdates;
66
67    private LayoutMode mLayoutMode;
68
69    public ContactDetailLayoutController(Context context, Bundle savedState,
70            FragmentManager fragmentManager, View viewContainer, ContactDetailFragmentListener
71            contactDetailFragmentListener) {
72
73        if (fragmentManager == null) {
74            throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
75                    + "without a non-null FragmentManager");
76        }
77
78        mLayoutInflater = (LayoutInflater) context.getSystemService(
79                Context.LAYOUT_INFLATER_SERVICE);
80        mFragmentManager = fragmentManager;
81        mContactDetailFragmentListener = contactDetailFragmentListener;
82
83        // Retrieve views in case this is view pager and carousel mode
84        mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager);
85        mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel);
86
87        // Retrieve views in case this is 2-column layout mode
88        mDetailFragmentView = viewContainer.findViewById(R.id.about_fragment_container);
89        mUpdatesFragmentView = viewContainer.findViewById(R.id.updates_fragment_container);
90
91        // Determine the layout mode based on whether the {@link ViewPager} is null or not. If the
92        // {@link ViewPager} is null, then this is a wide screen and the content can be displayed
93        // in 2 columns side by side. If the {@link ViewPager} is non-null, then this is a narrow
94        // screen and the user will need to swipe to see all the data.
95        mLayoutMode = (mViewPager == null) ? LayoutMode.TWO_COLUMN :
96                LayoutMode.VIEW_PAGER_AND_CAROUSEL;
97
98        initialize(savedState);
99    }
100
101    private void initialize(Bundle savedState) {
102        boolean fragmentsAddedToFragmentManager = true;
103        mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag(
104                ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
105        mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag(
106                ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
107
108        // If the detail fragment was found in the {@link FragmentManager} then we don't need to add
109        // it again. Otherwise, create the fragments dynamically and remember to add them to the
110        // {@link FragmentManager}.
111        if (mDetailFragment == null) {
112            mDetailFragment = new ContactDetailFragment();
113            mUpdatesFragment = new ContactDetailUpdatesFragment();
114            fragmentsAddedToFragmentManager = false;
115        }
116
117        mDetailFragment.setListener(mContactDetailFragmentListener);
118        mDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
119        mUpdatesFragment.setVerticalScrollListener(mVerticalScrollListener);
120
121        switch (mLayoutMode) {
122            case VIEW_PAGER_AND_CAROUSEL: {
123                mTabCarousel.setListener(mTabCarouselListener);
124                // Inflate 2 view containers to pass in as children to the {@link ViewPager},
125                // which will in turn be the parents to the mDetailFragment and mUpdatesFragment
126                // since the fragments must have the same parent view IDs in both landscape and
127                // portrait layouts.
128                ViewGroup detailContainer = (ViewGroup) mLayoutInflater.inflate(
129                        R.layout.contact_detail_about_fragment_container, mViewPager, false);
130                ViewGroup updatesContainer = (ViewGroup) mLayoutInflater.inflate(
131                        R.layout.contact_detail_updates_fragment_container, mViewPager, false);
132
133                mViewPagerAdapter = new ContactDetailViewPagerAdapter();
134                mViewPagerAdapter.setAboutFragmentView(detailContainer);
135                mViewPagerAdapter.setUpdatesFragmentView(updatesContainer);
136
137                mViewPager.addView(detailContainer);
138                mViewPager.addView(updatesContainer);
139                mViewPager.setAdapter(mViewPagerAdapter);
140                mViewPager.setOnPageChangeListener(mOnPageChangeListener);
141
142                FragmentTransaction transaction = mFragmentManager.beginTransaction();
143                if (!fragmentsAddedToFragmentManager) {
144                    transaction.add(R.id.about_fragment_container, mDetailFragment,
145                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
146                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
147                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
148                } else {
149                    transaction.show(mDetailFragment);
150                    transaction.show(mUpdatesFragment);
151                }
152                transaction.commit();
153                mFragmentManager.executePendingTransactions();
154                break;
155            }
156            case TWO_COLUMN: {
157                if (!fragmentsAddedToFragmentManager) {
158                    FragmentTransaction transaction = mFragmentManager.beginTransaction();
159                    transaction.add(R.id.about_fragment_container, mDetailFragment,
160                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
161                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
162                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
163                    transaction.commit();
164                    mFragmentManager.executePendingTransactions();
165                }
166            }
167        }
168
169        if (savedState != null) {
170            if (savedState.getBoolean(KEY_CONTACT_HAS_UPDATES)) {
171                showContactWithUpdates();
172            } else {
173                showContactWithoutUpdates();
174            }
175        }
176    }
177
178    public void setContactData(ContactLoader.Result data) {
179        mContactData = data;
180        mContactHasUpdates = !data.getStreamItems().isEmpty();
181        if (mContactHasUpdates) {
182            showContactWithUpdates();
183        } else {
184            showContactWithoutUpdates();
185        }
186    }
187
188    private void showContactWithUpdates() {
189        switch (mLayoutMode) {
190            case TWO_COLUMN: {
191                // Set the contact data (hide the static photo because the photo will already be in
192                // the header that scrolls with contact details).
193                mDetailFragment.setShowStaticPhoto(false);
194                // Show the updates fragment
195                mUpdatesFragmentView.setVisibility(View.VISIBLE);
196                break;
197            }
198            case VIEW_PAGER_AND_CAROUSEL: {
199                // Update and show the tab carousel
200                mTabCarousel.loadData(mContactData);
201                mTabCarousel.setVisibility(View.VISIBLE);
202                // Update ViewPager so that it has the max # of tabs (to show updates)
203                mViewPagerAdapter.setFragmentViewCount(FRAGMENT_COUNT);
204                break;
205            }
206            default:
207                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
208        }
209
210        if (mContactData != null) {
211            mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
212            mUpdatesFragment.setData(mContactData.getLookupUri(), mContactData);
213        }
214    }
215
216    private void showContactWithoutUpdates() {
217        switch (mLayoutMode) {
218            case TWO_COLUMN:
219                // Show the static photo which is next to the list of scrolling contact details
220                mDetailFragment.setShowStaticPhoto(true);
221                // Hide the updates fragment
222                mUpdatesFragmentView.setVisibility(View.GONE);
223                break;
224            case VIEW_PAGER_AND_CAROUSEL:
225                // Hide the tab carousel
226                mTabCarousel.setVisibility(View.GONE);
227                // Update ViewPager so that it only has 1 tab and switch to the first indexed tab
228                mViewPagerAdapter.setFragmentViewCount(1);
229                mViewPager.setCurrentItem(0);
230                break;
231            default:
232                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
233        }
234
235        if (mContactData != null) {
236            mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
237        }
238    }
239
240    public void onSaveInstanceState(Bundle outState) {
241        outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates);
242    }
243
244    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
245
246        @Override
247        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
248            // The user is horizontally dragging the {@link ViewPager}, so send
249            // these scroll changes to the tab carousel. Ignore these events though if the carousel
250            // is actually controlling the {@link ViewPager} scrolls because it will already be
251            // in the correct position.
252            if (mViewPager.isFakeDragging()) {
253                return;
254            }
255            int x = (int) ((position + positionOffset) *
256                    mTabCarousel.getAllowedHorizontalScrollLength());
257            mTabCarousel.scrollTo(x, 0);
258        }
259
260        @Override
261        public void onPageSelected(int position) {
262            // Since a new page has been selected by the {@link ViewPager},
263            // update the tab selection in the carousel.
264            mTabCarousel.setCurrentTab(position);
265        }
266
267        @Override
268        public void onPageScrollStateChanged(int state) {}
269
270    };
271
272    private ContactDetailTabCarousel.Listener mTabCarouselListener =
273            new ContactDetailTabCarousel.Listener() {
274
275        @Override
276        public void onTouchDown() {
277            // The user just started scrolling the carousel, so begin "fake dragging" the
278            // {@link ViewPager} if it's not already doing so.
279            if (mViewPager.isFakeDragging()) {
280                return;
281            }
282            mViewPager.beginFakeDrag();
283        }
284
285        @Override
286        public void onTouchUp() {
287            // The user just stopped scrolling the carousel, so stop "fake dragging" the
288            // {@link ViewPager} if was doing so before.
289            if (mViewPager.isFakeDragging()) {
290                mViewPager.endFakeDrag();
291            }
292        }
293
294        @Override
295        public void onScrollChanged(int l, int t, int oldl, int oldt) {
296            // The user is scrolling the carousel, so send the scroll deltas to the
297            // {@link ViewPager} so it can move in sync.
298            if (mViewPager.isFakeDragging()) {
299                mViewPager.fakeDragBy(oldl-l);
300            }
301        }
302
303        @Override
304        public void onTabSelected(int position) {
305            // The user selected a tab, so update the {@link ViewPager}
306            mViewPager.setCurrentItem(position);
307        }
308    };
309
310    private OnScrollListener mVerticalScrollListener = new OnScrollListener() {
311
312        @Override
313        public void onScroll(
314                AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
315            if (mTabCarousel == null) {
316                return;
317            }
318            // If the FIRST item is not visible on the screen, then the carousel must be pinned
319            // at the top of the screen.
320            if (firstVisibleItem != 0) {
321                mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength());
322                return;
323            }
324            View topView = view.getChildAt(firstVisibleItem);
325            if (topView == null) {
326                return;
327            }
328            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
329                    -mTabCarousel.getAllowedVerticalScrollLength());
330            mTabCarousel.setY(amtToScroll);
331        }
332
333        @Override
334        public void onScrollStateChanged(AbsListView view, int scrollState) {}
335
336    };
337}
338