ContactDetailLayoutController.java revision bed71bedbaae4d6b26b2b64db476bffa710753e5
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.activities.PeopleActivity.ContactDetailFragmentListener;
21
22import android.app.Fragment;
23import android.app.FragmentManager;
24import android.app.FragmentTransaction;
25import android.os.Bundle;
26import android.support.v13.app.FragmentPagerAdapter;
27import android.support.v4.view.ViewPager;
28import android.support.v4.view.ViewPager.OnPageChangeListener;
29import android.view.View;
30import android.widget.AbsListView;
31import android.widget.AbsListView.OnScrollListener;
32
33/**
34 * Determines the layout of the contact card.
35 */
36public class ContactDetailLayoutController {
37
38    public static final int FRAGMENT_COUNT = 2;
39
40    private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag";
41    private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag";
42
43    private String mDetailFragmentTag;
44    private String mUpdatesFragmentTag;
45
46    private enum LayoutMode {
47        TWO_COLUMN, VIEW_PAGER_AND_CAROUSEL,
48    }
49
50    private final FragmentManager mFragmentManager;
51
52    private ContactDetailFragment mContactDetailFragment;
53    private ContactDetailUpdatesFragment mContactDetailUpdatesFragment;
54
55    private final ViewPager mViewPager;
56    private final ContactDetailTabCarousel mTabCarousel;
57    private ContactDetailFragment mPagerContactDetailFragment;
58    private ContactDetailUpdatesFragment mPagerContactDetailUpdatesFragment;
59
60    private ContactDetailFragmentListener mContactDetailFragmentListener;
61
62    private ContactLoader.Result mContactData;
63
64    private boolean mIsInitialized;
65
66    private LayoutMode mLayoutMode;
67
68    public ContactDetailLayoutController(FragmentManager fragmentManager, ViewPager viewPager,
69            ContactDetailTabCarousel tabCarousel, ContactDetailFragmentListener
70            contactDetailFragmentListener) {
71        if (fragmentManager == null) {
72            throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
73                    + "without a non-null FragmentManager");
74        }
75
76        mFragmentManager = fragmentManager;
77        mViewPager = viewPager;
78        mTabCarousel = tabCarousel;
79        mContactDetailFragmentListener = contactDetailFragmentListener;
80
81        // Determine the layout based on whether the {@link ViewPager} is null or not. If the
82        // {@link ViewPager} is null, then this is a wide screen and the content can be displayed
83        // in 2 columns side by side. If the {@link ViewPager} is non-null, then this is a narrow
84        // screen and the user will need to swipe to see all the data.
85        mLayoutMode = (mViewPager == null) ? LayoutMode.TWO_COLUMN :
86                LayoutMode.VIEW_PAGER_AND_CAROUSEL;
87
88    }
89
90    public boolean isInitialized() {
91        return mIsInitialized;
92    }
93
94    public void initialize() {
95        mIsInitialized = true;
96        if (mDetailFragmentTag != null || mUpdatesFragmentTag != null) {
97            // Manually remove any {@link ViewPager} fragments if there was an orientation change
98            ContactDetailFragment oldDetailFragment = (ContactDetailFragment) mFragmentManager.
99                    findFragmentByTag(mDetailFragmentTag);
100            ContactDetailUpdatesFragment oldUpdatesFragment = (ContactDetailUpdatesFragment)
101                    mFragmentManager.findFragmentByTag(mUpdatesFragmentTag);
102
103            if (oldDetailFragment != null && oldUpdatesFragment != null) {
104                FragmentTransaction ft = mFragmentManager.beginTransaction();
105                ft.remove(oldDetailFragment);
106                ft.remove(oldUpdatesFragment);
107                ft.commitAllowingStateLoss();
108            }
109        }
110        if (mViewPager != null) {
111            mViewPager.setAdapter(new ViewPagerAdapter(mFragmentManager));
112            mViewPager.setOnPageChangeListener(mOnPageChangeListener);
113            mTabCarousel.setListener(mTabCarouselListener);
114        }
115    }
116
117    public void setContactDetailFragment(ContactDetailFragment contactDetailFragment) {
118        mContactDetailFragment = contactDetailFragment;
119    }
120
121    public void setContactDetailUpdatesFragment(ContactDetailUpdatesFragment updatesFragment) {
122        mContactDetailUpdatesFragment = updatesFragment;
123    }
124
125    public void setContactData(ContactLoader.Result data) {
126        mContactData = data;
127        if (!data.getStreamItems().isEmpty()) {
128            showContactWithUpdates();
129        } else {
130            showContactWithoutUpdates();
131        }
132    }
133
134    private void showContactWithUpdates() {
135        FragmentTransaction ft = mFragmentManager.beginTransaction();
136
137        switch (mLayoutMode) {
138            case TWO_COLUMN: {
139                // Set the contact data (hide the static photo because the photo will already be in
140                // the header that scrolls with contact details).
141                mContactDetailFragment.setShowStaticPhoto(false);
142                mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
143                mContactDetailUpdatesFragment.setData(mContactData.getLookupUri(), mContactData);
144
145                // Update fragment visibility
146                ft.show(mContactDetailUpdatesFragment);
147                break;
148            }
149            case VIEW_PAGER_AND_CAROUSEL: {
150                // Set the contact data
151                mTabCarousel.loadData(mContactData);
152                mPagerContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
153                mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(),
154                        mContactData);
155
156                // Update fragment and view visibility
157                mViewPager.setVisibility(View.VISIBLE);
158                mTabCarousel.setVisibility(View.VISIBLE);
159                ft.hide(mContactDetailFragment);
160                break;
161            }
162            default:
163                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
164        }
165
166        // If the activity has already saved its state, then allow this fragment
167        // transaction to be dropped because there's nothing else we can do to update the UI.
168        // The fact that the contact URI has already been saved by the activity means we can
169        // restore this later.
170        ft.commitAllowingStateLoss();
171    }
172
173    private void showContactWithoutUpdates() {
174        FragmentTransaction ft = mFragmentManager.beginTransaction();
175
176        switch (mLayoutMode) {
177            case TWO_COLUMN:
178                mContactDetailFragment.setShowStaticPhoto(true);
179                mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
180                ft.hide(mContactDetailUpdatesFragment);
181                break;
182            case VIEW_PAGER_AND_CAROUSEL:
183                mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData);
184                ft.show(mContactDetailFragment);
185                mViewPager.setVisibility(View.GONE);
186                mTabCarousel.setVisibility(View.GONE);
187                break;
188            default:
189                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
190        }
191
192        // If the activity has already saved its state, then allow this fragment
193        // transaction to be dropped because there's nothing else we can do to update the UI.
194        // The fact that the contact URI has already been saved by the activity means we can
195        // restore this later.
196        ft.commitAllowingStateLoss();
197    }
198
199    public void onSaveInstanceState(Bundle outState) {
200        if (mPagerContactDetailFragment != null) {
201            outState.putString(KEY_DETAIL_FRAGMENT_TAG,
202                    mPagerContactDetailFragment.getTag());
203            outState.putString(KEY_UPDATES_FRAGMENT_TAG,
204                    mPagerContactDetailUpdatesFragment.getTag());
205        }
206    }
207
208    public void onRestoreInstanceState(Bundle savedState) {
209        mDetailFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG);
210        mUpdatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG);
211    }
212
213    public class ViewPagerAdapter extends FragmentPagerAdapter{
214
215        public ViewPagerAdapter(FragmentManager fm) {
216            super(fm);
217        }
218
219        @Override
220        public Fragment getItem(int position) {
221            switch (position) {
222                case 0:
223                    mPagerContactDetailFragment = new ContactDetailFragment();
224                    if (mContactData != null) {
225                        mPagerContactDetailFragment.setData(mContactData.getLookupUri(),
226                                mContactData);
227                    }
228                    mPagerContactDetailFragment.setListener(mContactDetailFragmentListener);
229                    mPagerContactDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
230                    return mPagerContactDetailFragment;
231                case 1:
232                    mPagerContactDetailUpdatesFragment = new ContactDetailUpdatesFragment();
233                    if (mContactData != null) {
234                        mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(),
235                                mContactData);
236                    }
237                    return mPagerContactDetailUpdatesFragment;
238            }
239            throw new IllegalStateException("No fragment at position " + position);
240        }
241
242        @Override
243        public int getCount() {
244            return FRAGMENT_COUNT;
245        }
246    }
247
248    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
249
250        @Override
251        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
252            // The user is horizontally dragging the {@link ViewPager}, so send
253            // these scroll changes to the tab carousel. Ignore these events though if the carousel
254            // is actually controlling the {@link ViewPager} scrolls because it will already be
255            // in the correct position.
256            if (mViewPager.isFakeDragging()) {
257                return;
258            }
259            int x = (int) ((position + positionOffset) *
260                    mTabCarousel.getAllowedHorizontalScrollLength());
261            mTabCarousel.scrollTo(x, 0);
262        }
263
264        @Override
265        public void onPageSelected(int position) {
266            // Since a new page has been selected by the {@link ViewPager},
267            // update the tab selection in the carousel.
268            mTabCarousel.setCurrentTab(position);
269        }
270
271        @Override
272        public void onPageScrollStateChanged(int state) {}
273
274    };
275
276    private ContactDetailTabCarousel.Listener mTabCarouselListener =
277            new ContactDetailTabCarousel.Listener() {
278
279        @Override
280        public void onTouchDown() {
281            // The user just started scrolling the carousel, so begin "fake dragging" the
282            // {@link ViewPager} if it's not already doing so.
283            if (mViewPager.isFakeDragging()) {
284                return;
285            }
286            mViewPager.beginFakeDrag();
287        }
288
289        @Override
290        public void onTouchUp() {
291            // The user just stopped scrolling the carousel, so stop "fake dragging" the
292            // {@link ViewPager} if was doing so before.
293            if (mViewPager.isFakeDragging()) {
294                mViewPager.endFakeDrag();
295            }
296        }
297
298        @Override
299        public void onScrollChanged(int l, int t, int oldl, int oldt) {
300            // The user is scrolling the carousel, so send the scroll deltas to the
301            // {@link ViewPager} so it can move in sync.
302            if (mViewPager.isFakeDragging()) {
303                mViewPager.fakeDragBy(oldl-l);
304            }
305        }
306
307        @Override
308        public void onTabSelected(int position) {
309            // The user selected a tab, so update the {@link ViewPager}
310            mViewPager.setCurrentItem(position);
311        }
312    };
313
314    private OnScrollListener mVerticalScrollListener = new OnScrollListener() {
315
316        @Override
317        public void onScroll(
318                AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
319            if (mTabCarousel == null) {
320                return;
321            }
322            // If the FIRST item is not visible on the screen, then the carousel must be pinned
323            // at the top of the screen.
324            if (firstVisibleItem != 0) {
325                mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength());
326                return;
327            }
328            View topView = view.getChildAt(firstVisibleItem);
329            if (topView == null) {
330                return;
331            }
332            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
333                    -mTabCarousel.getAllowedVerticalScrollLength());
334            mTabCarousel.setY(amtToScroll);
335        }
336
337        @Override
338        public void onScrollStateChanged(AbsListView view, int scrollState) {}
339
340    };
341}
342