Home | History | Annotate | Download | only in editor
      1 /*
      2  * Copyright (C) 2009 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 
     17 package com.android.contacts.editor;
     18 
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.graphics.Bitmap;
     22 import android.net.Uri;
     23 import android.provider.ContactsContract.CommonDataKinds.Photo;
     24 import android.provider.ContactsContract.Data;
     25 import android.text.TextUtils;
     26 import android.util.AttributeSet;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.widget.ImageView;
     30 import android.widget.LinearLayout;
     31 import android.widget.TextView;
     32 
     33 import com.android.contacts.R;
     34 import com.android.contacts.common.model.RawContactDelta;
     35 import com.android.contacts.common.model.ValuesDelta;
     36 import com.android.contacts.common.model.RawContactModifier;
     37 import com.android.contacts.common.model.account.AccountType;
     38 import com.android.contacts.common.model.account.AccountType.EditType;
     39 import com.android.contacts.common.model.account.AccountWithDataSet;
     40 
     41 /**
     42  * Base view that provides common code for the editor interaction for a specific
     43  * RawContact represented through an {@link RawContactDelta}.
     44  * <p>
     45  * Internal updates are performed against {@link ValuesDelta} so that the
     46  * source {@link RawContact} can be swapped out. Any state-based changes, such as
     47  * adding {@link Data} rows or changing {@link EditType}, are performed through
     48  * {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
     49  */
     50 public abstract class BaseRawContactEditorView extends LinearLayout {
     51 
     52     private PhotoEditorView mPhoto;
     53 
     54     private View mAccountHeaderContainer;
     55     private ImageView mExpandAccountButton;
     56     private LinearLayout mCollapsibleSection;
     57     private TextView mAccountName;
     58     private TextView mAccountType;
     59 
     60     protected Listener mListener;
     61 
     62     public interface Listener {
     63         void onExternalEditorRequest(AccountWithDataSet account, Uri uri);
     64         void onEditorExpansionChanged();
     65     }
     66 
     67     public BaseRawContactEditorView(Context context) {
     68         super(context);
     69     }
     70 
     71     public BaseRawContactEditorView(Context context, AttributeSet attrs) {
     72         super(context, attrs);
     73     }
     74 
     75     @Override
     76     protected void onFinishInflate() {
     77         super.onFinishInflate();
     78 
     79         mPhoto = (PhotoEditorView)findViewById(R.id.edit_photo);
     80         mPhoto.setEnabled(isEnabled());
     81 
     82         mAccountHeaderContainer = findViewById(R.id.account_header_container);
     83         mExpandAccountButton = (ImageView) findViewById(R.id.expand_account_button);
     84         mCollapsibleSection = (LinearLayout) findViewById(R.id.collapsable_section);
     85         mAccountName = (TextView) findViewById(R.id.account_name);
     86         mAccountType = (TextView) findViewById(R.id.account_type);
     87 
     88         setCollapsed(false);
     89         setCollapsible(true);
     90     }
     91 
     92     public void setGroupMetaData(Cursor groupMetaData) {
     93     }
     94 
     95 
     96     public void setListener(Listener listener) {
     97         mListener = listener;
     98     }
     99 
    100     /**
    101      * Assign the given {@link Bitmap} to the internal {@link PhotoEditorView}
    102      * in order to update the {@link RawContactDelta} currently being edited.
    103      */
    104     public void setPhotoEntry(Bitmap bitmap) {
    105         mPhoto.setPhotoEntry(bitmap);
    106     }
    107 
    108     /**
    109      * Assign the given photo {@link Uri} to UI of the {@link PhotoEditorView}, so that it can
    110      * display a full sized photo.
    111      */
    112     public void setFullSizedPhoto(Uri uri) {
    113         mPhoto.setFullSizedPhoto(uri);
    114     }
    115 
    116     protected void setHasPhotoEditor(boolean hasPhotoEditor) {
    117         mPhoto.setVisibility(hasPhotoEditor ? View.VISIBLE : View.GONE);
    118     }
    119 
    120     /**
    121      * Return true if internal {@link PhotoEditorView} has a {@link Photo} set.
    122      */
    123     public boolean hasSetPhoto() {
    124         return mPhoto.hasSetPhoto();
    125     }
    126 
    127     public PhotoEditorView getPhotoEditor() {
    128         return mPhoto;
    129     }
    130 
    131     /**
    132      * @return the RawContact ID that this editor is editing.
    133      */
    134     public abstract long getRawContactId();
    135 
    136     /**
    137      * If {@param isCollapsible} is TRUE, then this editor can be collapsed by clicking on its
    138      * account header.
    139      */
    140     public void setCollapsible(boolean isCollapsible) {
    141         if (isCollapsible) {
    142             mAccountHeaderContainer.setOnClickListener(new OnClickListener() {
    143                 @Override
    144                 public void onClick(View v) {
    145                     final int startingHeight = mCollapsibleSection.getMeasuredHeight();
    146                     final boolean isCollapsed = isCollapsed();
    147                     setCollapsed(!isCollapsed);
    148                     // The slideAndFadeIn animation only looks good when collapsing. For expanding,
    149                     // it looks like the editor is loading sluggishly. I tried animating the
    150                     // clipping bounds instead of the alpha value. But because the editors are very
    151                     // tall, this animation looked very similar to doing no animation at all. It
    152                     // wasn't worth the significant additional complexity.
    153                     if (!isCollapsed) {
    154                         EditorAnimator.getInstance().slideAndFadeIn(mCollapsibleSection,
    155                                 startingHeight);
    156                         // We want to place the focus near the top of the screen now that a
    157                         // potentially focused editor is being collapsed.
    158                         EditorAnimator.placeFocusAtTopOfScreenAfterReLayout(mCollapsibleSection);
    159                     } else {
    160                         // When expanding we should scroll the expanded view onto the screen.
    161                         // Otherwise, user's may not notice that any expansion happened.
    162                         EditorAnimator.getInstance().scrollViewToTop(mAccountHeaderContainer);
    163                         mCollapsibleSection.requestFocus();
    164                     }
    165                     if (mListener != null) {
    166                         mListener.onEditorExpansionChanged();
    167                     }
    168                     updateAccountHeaderContentDescription();
    169                 }
    170             });
    171             mExpandAccountButton.setVisibility(View.VISIBLE);
    172             mAccountHeaderContainer.setClickable(true);
    173         } else {
    174             mAccountHeaderContainer.setOnClickListener(null);
    175             mExpandAccountButton.setVisibility(View.GONE);
    176             mAccountHeaderContainer.setClickable(false);
    177         }
    178     }
    179 
    180     public boolean isCollapsed() {
    181         return mCollapsibleSection.getLayoutParams().height == 0;
    182     }
    183 
    184     public void setCollapsed(boolean isCollapsed) {
    185         final LinearLayout.LayoutParams params
    186                 = (LayoutParams) mCollapsibleSection.getLayoutParams();
    187         if (isCollapsed) {
    188             params.height = 0;
    189             mCollapsibleSection.setLayoutParams(params);
    190             mExpandAccountButton.setImageResource(R.drawable.ic_menu_expander_minimized_holo_light);
    191         } else {
    192             params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    193             mCollapsibleSection.setLayoutParams(params);
    194             mExpandAccountButton.setImageResource(R.drawable.ic_menu_expander_maximized_holo_light);
    195         }
    196     }
    197 
    198     protected void updateAccountHeaderContentDescription() {
    199         final StringBuilder builder = new StringBuilder();
    200         builder.append(EditorUiUtils.getAccountInfoContentDescription(
    201                 mAccountName.getText(), mAccountType.getText()));
    202         if (mExpandAccountButton.getVisibility() == View.VISIBLE) {
    203             builder.append(getResources().getString(isCollapsed()
    204                     ? R.string.content_description_expand_editor
    205                     : R.string.content_description_collapse_editor));
    206         }
    207         mAccountHeaderContainer.setContentDescription(builder);
    208     }
    209 
    210     /**
    211      * Set the internal state for this view, given a current
    212      * {@link RawContactDelta} state and the {@link AccountType} that
    213      * apply to that state.
    214      */
    215     public abstract void setState(RawContactDelta state, AccountType source, ViewIdGenerator vig,
    216             boolean isProfile);
    217 }
    218