Phone landscape view of contact card
- No tab carousel, show half of the other (about or updates) page,
tap or swipe to see the other page
- Add alpha layer and touch interceptor layer
- Add new resources folder for w470dp to cover landscape on
phone sized devices
Change-Id: Ia3b1cd76ebe35420b1facd415998b14ba161b0ba
diff --git a/res/layout-w470dp/contact_detail_activity.xml b/res/layout-w470dp/contact_detail_activity.xml
new file mode 100644
index 0000000..bf649a2
--- /dev/null
+++ b/res/layout-w470dp/contact_detail_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contact_detail_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.contacts.detail.ContactDetailFragmentCarousel
+ android:id="@+id/fragment_carousel"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</FrameLayout>
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
new file mode 100644
index 0000000..64fec60
--- /dev/null
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contact_detail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView android:id="@+id/photo"
+ android:scaleType="centerCrop"
+ android:layout_width="100dip"
+ android:layout_height="100dip"
+ android:layout_marginLeft="@dimen/detail_contact_photo_margin"
+ android:layout_marginRight="@dimen/detail_contact_photo_margin"
+ android:layout_marginTop="@dimen/detail_contact_photo_margin"
+ android:layout_marginBottom="@dimen/detail_contact_photo_margin"/>
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:divider="@null"/>
+
+ </LinearLayout>
+
+ <ScrollView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:visibility="gone">
+ <TextView android:id="@+id/emptyText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_contact_details"
+ android:textSize="20sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:paddingTop="10dip"
+ android:lineSpacingMultiplier="0.92"/>
+ </ScrollView>
+
+ <!-- "Copy to my contacts"- button -->
+ <Button
+ android:id="@+id/copyLocal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/menu_copyContact"
+ android:visibility="gone"
+ android:layout_gravity="right"
+ android:layout_marginRight="40dip"
+ android:layout_marginTop="20dip"
+ android:layout_marginBottom="20dip" />
+
+ <View
+ android:id="@+id/alpha_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:background="@android:color/black"
+ android:alpha=".50"
+ android:visibility="gone"/>
+
+ <View
+ android:id="@+id/touch_intercept_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:background="@android:color/transparent"
+ android:visibility="gone"/>
+</RelativeLayout>
+
diff --git a/res/layout/contact_detail_fragment_carousel.xml b/res/layout/contact_detail_fragment_carousel.xml
new file mode 100644
index 0000000..165b6a1
--- /dev/null
+++ b/res/layout/contact_detail_fragment_carousel.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none"
+ android:orientation="horizontal">
+
+ <fragment class="com.android.contacts.detail.ContactDetailAboutFragment"
+ android:id="@+id/about_fragment"
+ android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
+ android:layout_height="match_parent" />
+
+ <fragment class="com.android.contacts.detail.ContactDetailUpdatesFragment"
+ android:id="@+id/updates_fragment"
+ android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
index f60be2a..daa5608 100644
--- a/res/layout/contact_detail_updates_fragment.xml
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contact_detail"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -30,5 +30,20 @@
android:paddingRight="10dip"
android:paddingTop="@dimen/detail_tab_carousel_height"
android:layout_marginTop="10dip"/>
-</LinearLayout>
+
+ <View
+ android:id="@+id/alpha_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:alpha=".50"
+ android:visibility="gone"/>
+
+ <View
+ android:id="@+id/touch_intercept_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:visibility="gone"/>
+</FrameLayout>
diff --git a/res/values-w470dp/dimens.xml b/res/values-w470dp/dimens.xml
new file mode 100644
index 0000000..ba7f3f8
--- /dev/null
+++ b/res/values-w470dp/dimens.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ 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.
+-->
+<resources>
+ <dimen name="detail_tab_carousel_height">0dip</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e55ec34..3e5b554 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -97,12 +97,18 @@
<!-- Height of the tab text label in the tab carousel on the contact detail page -->
<dimen name="detail_tab_carousel_tab_label_height">40dip</dimen>
+ <!-- Width of one fragment in the fragment carousel on the contact detail page -->
+ <dimen name="detail_fragment_carousel_fragment_width">420dip</dimen>
+
<!-- Vertical margin of the text within the update tab in the tab carousel -->
<dimen name="detail_update_tab_vertical_margin">20dip</dimen>
<!-- Left and right padding of the text within the update tab in the tab carousel -->
<dimen name="detail_update_tab_side_padding">10dip</dimen>
+ <!-- Margin around the contact's photo on the contact card -->
+ <dimen name="detail_contact_photo_margin">15dip</dimen>
+
<!-- Left and right padding for a contact detail item -->
<dimen name="detail_item_icon_margin">10dip</dimen>
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 1488bec..c62084b 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -23,11 +23,10 @@
import com.android.contacts.R;
import com.android.contacts.detail.ContactDetailAboutFragment;
import com.android.contacts.detail.ContactDetailFragment;
-import com.android.contacts.detail.ContactDetailHeaderView;
+import com.android.contacts.detail.ContactDetailFragmentCarousel;
import com.android.contacts.detail.ContactDetailTabCarousel;
import com.android.contacts.detail.ContactDetailUpdatesFragment;
import com.android.contacts.interactions.ContactDeletionInteraction;
-import com.android.contacts.list.ContactBrowseListFragment;
import com.android.contacts.util.PhoneCapabilityTester;
import android.accounts.Account;
@@ -43,9 +42,7 @@
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Toast;
@@ -63,12 +60,11 @@
private ContactDetailTabCarousel mTabCarousel;
private ViewPager mViewPager;
- private Uri mUri;
+ private ContactDetailFragmentCarousel mFragmentCarousel;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
-
if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
// This activity must not be shown. We have to select the contact in the
// PeopleActivity instead ==> Create a forward intent and finish
@@ -88,25 +84,35 @@
setContentView(R.layout.contact_detail_activity);
+ // Narrow width screens have a {@link ViewPager} and {@link ContactDetailTabCarousel}
mViewPager = (ViewPager) findViewById(R.id.pager);
- mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
- mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+ if (mViewPager != null) {
+ mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+ mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+ }
mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
- mTabCarousel.setListener(mTabCarouselListener);
+ if (mTabCarousel != null) {
+ mTabCarousel.setListener(mTabCarouselListener);
+ }
- mUri = getIntent().getData();
+ // Otherwise, wide width screens have a {@link ContactDetailFragmentCarousel}
+ mFragmentCarousel = (ContactDetailFragmentCarousel) findViewById(R.id.fragment_carousel);
+ if (mFragmentCarousel != null) {
+ if (mAboutFragment != null) mFragmentCarousel.setAboutFragment(mAboutFragment);
+ if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment);
+ }
+
Log.i(TAG, getIntent().getData().toString());
}
-
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof ContactDetailAboutFragment) {
mAboutFragment = (ContactDetailAboutFragment) fragment;
mAboutFragment.setListener(mFragmentListener);
mAboutFragment.setVerticalScrollListener(mVerticalScrollListener);
- mAboutFragment.loadUri(mUri);
+ mAboutFragment.loadUri(getIntent().getData());
} else if (fragment instanceof ContactDetailUpdatesFragment) {
mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
}
@@ -125,7 +131,7 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
FragmentKeyListener mCurrentFragment;
- switch (mViewPager.getCurrentItem()) {
+ switch (getCurrentPage()) {
case 0:
mCurrentFragment = (FragmentKeyListener) mAboutFragment;
break;
@@ -140,6 +146,16 @@
return super.onKeyDown(keyCode, event);
}
+ private int getCurrentPage() {
+ if (mViewPager != null) {
+ return mViewPager.getCurrentItem();
+ } else if (mFragmentCarousel != null) {
+ return mFragmentCarousel.getCurrentPage();
+ }
+ throw new IllegalStateException("Can't figure out the currently selected page. The activity"
+ + "must either have the ViewPager or fragment carousel");
+ }
+
private final ContactDetailFragment.Listener mFragmentListener =
new ContactDetailFragment.Listener() {
@Override
@@ -149,7 +165,9 @@
@Override
public void onDetailsLoaded(ContactLoader.Result result) {
- mTabCarousel.loadData(result);
+ if (mTabCarousel != null) {
+ mTabCarousel.loadData(result);
+ }
}
@Override
@@ -278,6 +296,9 @@
@Override
public void onScroll(
AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (mTabCarousel == null) {
+ return;
+ }
// Only re-position the tab carousel vertically if the FIRST item is still visible on
// the screen, otherwise the carousel should be in the correct place (pinned at the
// top).
diff --git a/src/com/android/contacts/detail/ContactDetailAboutFragment.java b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
index a1377e8..fc6b9cb 100644
--- a/src/com/android/contacts/detail/ContactDetailAboutFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
@@ -19,20 +19,12 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
-import android.accounts.Account;
import android.app.ActionBar;
import android.app.Activity;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import java.util.ArrayList;
-
public class ContactDetailAboutFragment extends ContactDetailFragment {
private static final String TAG = "ContactDetailAboutFragment";
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 74c05a5..c2aaea4 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -109,7 +109,7 @@
import java.util.Collections;
import java.util.List;
-public class ContactDetailFragment extends Fragment implements FragmentKeyListener,
+public class ContactDetailFragment extends Fragment implements FragmentKeyListener, FragmentOverlay,
OnItemClickListener, OnItemLongClickListener, SelectAccountDialogFragment.Listener {
private static final String TAG = "ContactDetailFragment";
@@ -128,6 +128,7 @@
private ContactLoader.Result mContactData;
private ContactDetailHeaderView mHeaderView;
+ private ImageView mPhotoView;
private ListView mListView;
private ViewAdapter mAdapter;
private Uri mPrimaryPhoneUri = null;
@@ -166,6 +167,17 @@
private View mEmptyView;
/**
+ * This optional view adds an alpha layer over the entire fragment.
+ */
+ private View mAlphaLayer;
+
+ /**
+ * This optional view adds a layer over the entire fragment so that when visible, it intercepts
+ * all touch events on the fragment.
+ */
+ private View mTouchInterceptLayer;
+
+ /**
* A list of distinct contact IDs included in the current contact.
*/
private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
@@ -233,6 +245,8 @@
mInflater = inflater;
+ mPhotoView = (ImageView) mView.findViewById(R.id.photo);
+
mListView = (ListView) mView.findViewById(android.R.id.list);
mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
mListView.setOnItemClickListener(this);
@@ -242,6 +256,9 @@
// Don't set it to mListView yet. We do so later when we bind the adapter.
mEmptyView = mView.findViewById(android.R.id.empty);
+ mAlphaLayer = mView.findViewById(R.id.alpha_overlay);
+ mTouchInterceptLayer = mView.findViewById(R.id.touch_intercept_overlay);
+
mCopyGalToLocalButton = (Button) mView.findViewById(R.id.copyLocal);
mCopyGalToLocalButton.setOnClickListener(new OnClickListener() {
@Override
@@ -262,6 +279,35 @@
mListener = value;
}
+ @Override
+ public void setAlphaLayerValue(float alpha) {
+ if (mAlphaLayer != null) {
+ mAlphaLayer.setAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void enableAlphaLayer() {
+ if (mAlphaLayer != null) {
+ mAlphaLayer.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void enableTouchInterceptor(OnClickListener clickListener) {
+ if (mTouchInterceptLayer != null) {
+ mTouchInterceptLayer.setVisibility(View.VISIBLE);
+ mTouchInterceptLayer.setOnClickListener(clickListener);
+ }
+ }
+
+ @Override
+ public void disableTouchInterceptor() {
+ if (mTouchInterceptLayer != null) {
+ mTouchInterceptLayer.setVisibility(View.GONE);
+ }
+ }
+
protected Context getContext() {
return mContext;
}
@@ -335,6 +381,11 @@
// Clear old header
mHeaderView = null;
+ // Setup the photo if applicable
+ if (mPhotoView != null) {
+ ContactDetailDisplayUtils.setPhoto(mContext, mContactData, mPhotoView);
+ }
+
// Build up the contact entries
buildEntries();
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
new file mode 100644
index 0000000..da64c34
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.contacts.detail;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.HorizontalScrollView;
+
+/**
+ * This is a horizontally scrolling carousel with 2 fragments: one to see info about the contact and
+ * one to see updates from the contact. Depending on the scroll position and user selection of which
+ * fragment to currently view, the alpha values and touch interceptors over each fragment are
+ * configured accordingly.
+ */
+public class ContactDetailFragmentCarousel extends HorizontalScrollView implements OnTouchListener {
+
+ private static final String TAG = ContactDetailFragmentCarousel.class.getSimpleName();
+
+ private int mAllowedHorizontalScrollLength = Integer.MIN_VALUE;
+ private int mLowerThreshold = Integer.MIN_VALUE;
+ private int mUpperThreshold = Integer.MIN_VALUE;
+
+ private static final int ABOUT_PAGE = 0;
+ private static final int UPDATES_PAGE = 1;
+
+ private int mCurrentPage = ABOUT_PAGE;
+ private int mLastScrollPosition;
+
+ private FragmentOverlay mAboutFragment;
+ private FragmentOverlay mUpdatesFragment;
+
+ private static final float MAX_ALPHA = 0.5f;
+
+ public ContactDetailFragmentCarousel(Context context) {
+ this(context, null);
+ }
+
+ public ContactDetailFragmentCarousel(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ContactDetailFragmentCarousel(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.contact_detail_fragment_carousel, this);
+
+ setOnTouchListener(this);
+ }
+
+ public void setAboutFragment(FragmentOverlay fragment) {
+ // TODO: We can't always assume the "about" page will be the current page.
+ mAboutFragment = fragment;
+ mAboutFragment.enableAlphaLayer();
+ mAboutFragment.setAlphaLayerValue(0);
+ mAboutFragment.disableTouchInterceptor();
+ }
+
+ public void setUpdatesFragment(FragmentOverlay fragment) {
+ mUpdatesFragment = fragment;
+ mUpdatesFragment.enableAlphaLayer();
+ mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA);
+ mUpdatesFragment.enableTouchInterceptor(mUpdatesFragTouchInterceptListener);
+ }
+
+ public int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ private final OnClickListener mAboutFragTouchInterceptListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCurrentPage = ABOUT_PAGE;
+ snapToEdge();
+ }
+ };
+
+ private final OnClickListener mUpdatesFragTouchInterceptListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCurrentPage = UPDATES_PAGE;
+ snapToEdge();
+ }
+ };
+
+ private void updateTouchInterceptors() {
+ switch (mCurrentPage) {
+ case ABOUT_PAGE:
+ // The "about this contact" page has been selected, so disable the touch interceptor
+ // on this page and enable it for the "updates" page.
+ mAboutFragment.disableTouchInterceptor();
+ mUpdatesFragment.enableTouchInterceptor(mUpdatesFragTouchInterceptListener);
+ break;
+ case UPDATES_PAGE:
+ mUpdatesFragment.disableTouchInterceptor();
+ mAboutFragment.enableTouchInterceptor(mAboutFragTouchInterceptListener);
+ break;
+ }
+ }
+
+ private void updateAlphaLayers() {
+ mAboutFragment.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
+ getAllowedHorizontalScrollLength());
+ mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
+ getAllowedHorizontalScrollLength());
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ mLastScrollPosition= l;
+ updateAlphaLayers();
+ }
+
+ private void snapToEdge() {
+ switch (mCurrentPage) {
+ case ABOUT_PAGE:
+ smoothScrollTo(0, 0);
+ break;
+ case UPDATES_PAGE:
+ smoothScrollTo(getAllowedHorizontalScrollLength(), 0);
+ break;
+ }
+ updateTouchInterceptors();
+ }
+
+ /**
+ * Returns the desired page we should scroll to based on the current X scroll position and the
+ * current page.
+ */
+ private int getDesiredPage() {
+ switch (mCurrentPage) {
+ case ABOUT_PAGE:
+ // If the user is on the "about" page, and the scroll position exceeds the lower
+ // threshold, then we should switch to the "updates" page.
+ return (mLastScrollPosition > getLowerThreshold()) ? UPDATES_PAGE : ABOUT_PAGE;
+ case UPDATES_PAGE:
+ // If the user is on the "updates" page, and the scroll position goes below the
+ // upper threshold, then we should switch to the "about" page.
+ return (mLastScrollPosition < getUpperThreshold()) ? ABOUT_PAGE : UPDATES_PAGE;
+ }
+ throw new IllegalStateException("Invalid current page " + mCurrentPage);
+ }
+
+ /**
+ * Returns the number of pixels that this view can be scrolled horizontally.
+ */
+ private int getAllowedHorizontalScrollLength() {
+ if (mAllowedHorizontalScrollLength == Integer.MIN_VALUE) {
+ computeThresholds();
+ }
+ return mAllowedHorizontalScrollLength;
+ }
+
+ /**
+ * Returns the minimum X scroll position that must be surpassed (if the user is on the "about"
+ * page of the contact card), in order for this view to automatically snap to the "updates"
+ * page.
+ */
+ private int getLowerThreshold() {
+ if (mLowerThreshold == Integer.MIN_VALUE) {
+ computeThresholds();
+ }
+ return mLowerThreshold;
+ }
+
+ /**
+ * Returns the maximum X scroll position (if the user is on the "updates" page of the contact
+ * card), below which this view will automatically snap to the "about" page.
+ */
+ private int getUpperThreshold() {
+ if (mLowerThreshold == Integer.MIN_VALUE) {
+ computeThresholds();
+ }
+ return mUpperThreshold;
+ }
+
+ // TODO: Move this to a Fragment override method (i.e. onActivityCreated or some method where
+ // we can be sure the width of the views are non-zero) instead of doing it on the fly when the
+ // values are requested for the first time.
+ private void computeThresholds() {
+ int screenWidth = getWidth();
+ int fragmentWidth = findViewById(R.id.about_fragment).getWidth();
+ mAllowedHorizontalScrollLength = (2 * fragmentWidth) - screenWidth;
+ mLowerThreshold = (screenWidth - fragmentWidth) / 2;
+ mUpperThreshold = mAllowedHorizontalScrollLength - mLowerThreshold;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ mCurrentPage = getDesiredPage();
+ snapToEdge();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index dc3e126..a12106f 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -19,12 +19,10 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.ContactSaveService;
import com.android.contacts.R;
-import com.android.contacts.activities.ContactDetailActivity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 02678de..0c4dc4f 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -23,19 +23,67 @@
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
-public class ContactDetailUpdatesFragment extends Fragment implements FragmentKeyListener {
+public class ContactDetailUpdatesFragment extends Fragment
+ implements FragmentKeyListener, FragmentOverlay {
private static final String TAG = "ContactDetailUpdatesFragment";
+ /**
+ * This optional view adds an alpha layer over the entire fragment.
+ */
+ private View mAlphaLayer;
+
+ /**
+ * This optional view adds a layer over the entire fragment so that when visible, it intercepts
+ * all touch events on the fragment.
+ */
+ private View mTouchInterceptLayer;
+
public ContactDetailUpdatesFragment() {
// Explicit constructor for inflation
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
- return inflater.inflate(R.layout.contact_detail_updates_fragment, container, false);
+ View rootView = inflater.inflate(R.layout.contact_detail_updates_fragment, container,
+ false);
+
+ mAlphaLayer = rootView.findViewById(R.id.alpha_overlay);
+ mTouchInterceptLayer = rootView.findViewById(R.id.touch_intercept_overlay);
+
+ return rootView;
+ }
+
+ @Override
+ public void setAlphaLayerValue(float alpha) {
+ if (mAlphaLayer != null) {
+ mAlphaLayer.setAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void enableAlphaLayer() {
+ if (mAlphaLayer != null) {
+ mAlphaLayer.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void enableTouchInterceptor(OnClickListener clickListener) {
+ if (mTouchInterceptLayer != null) {
+ mTouchInterceptLayer.setVisibility(View.VISIBLE);
+ mTouchInterceptLayer.setOnClickListener(clickListener);
+ }
+ }
+
+ @Override
+ public void disableTouchInterceptor() {
+ if (mTouchInterceptLayer != null) {
+ mTouchInterceptLayer.setVisibility(View.GONE);
+ }
}
@Override
diff --git a/src/com/android/contacts/detail/FragmentOverlay.java b/src/com/android/contacts/detail/FragmentOverlay.java
new file mode 100644
index 0000000..6ef0846
--- /dev/null
+++ b/src/com/android/contacts/detail/FragmentOverlay.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.contacts.detail;
+
+import android.view.View.OnClickListener;
+
+/**
+ * This is implemented by {@link Fragment}s that contain an alpha layer and touch interceptor layer.
+ * The alpha layer covers the entire fragment and has an alpha value which makes the fragment
+ * contents appear "dimmed" out. The touch interceptor layer covers the entire fragment so that
+ * when visible, it intercepts all touch events on the {@link Fragment}.
+ */
+public interface FragmentOverlay {
+
+ /**
+ * Sets the alpha value on the alpha layer (if there is one).
+ */
+ public void setAlphaLayerValue(float alpha);
+
+ /**
+ * Makes the alpha layer on this fragment visible (if there is one).
+ */
+ public void enableAlphaLayer();
+
+ /**
+ * Makes the touch intercept layer on this fragment visible (if there is one). Also adds a click
+ * listener which is called when there is a touch event on the layer.
+ */
+ public void enableTouchInterceptor(OnClickListener clickListener);
+
+ /**
+ * Makes the touch intercept layer on this fragment gone (if there is one).
+ */
+ public void disableTouchInterceptor();
+}