Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** 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.facebook.widget;import android.app.Activity;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.content.res.TypedArray;import android.graphics.Color;import android.os.Bundle;import android.support.v4.content.LocalBroadcastManager;import android.util.AttributeSet;import android.util.TypedValue;import android.view.Gravity;import android.view.View;import android.widget.FrameLayout;import android.widget.LinearLayout;import android.widget.TextView;import com.facebook.android.R;import com.facebook.internal.*;/*** This class provides the UI for displaying the Facebook Like button and its associated components.*/public class LikeView extends FrameLayout {// ***// Keep all the enum values in sync with attrs.xml// ***/*** Encapsulates the valid values for the facebook:style attribute for a LikeView*/public enum Style {/*** Setting the attribute to this value will display the button and a sentence near it that describes the* social sentence for the associated object.** This is the default value*/STANDARD("standard", 0),/*** Setting the attribute to this value will display the button by itself, with no other components*/BUTTON("button", 1),/*** Setting the attribute to this value will display the button and a box near it with the number of likes* for the associated object*/BOX_COUNT("box_count", 2);static Style DEFAULT = STANDARD;static Style fromInt(int enumValue) {for (Style style : values()) {if (style.getValue() == enumValue) {return style;}}return null;}private String stringValue;private int intValue;private Style(String stringValue, int value) {this.stringValue = stringValue;this.intValue = value;}@Overridepublic String toString() {return stringValue;}private int getValue() {return intValue;}}/*** Encapsulates the valid values for the facebook:horizontal_alignment attribute for a LikeView.*/public enum HorizontalAlignment {/*** Setting the attribute to this value will center the button and auxiliary view in the parent view.** This is the default value*/CENTER("center", 0),/*** Setting the attribute to this value will left-justify the button and auxiliary view in the parent view.*/LEFT("left", 1),/*** Setting the attribute to this value will right-justify the button and auxiliary view in the parent view.* If the facebook:auxiliary_view_position is set to INLINE, then the auxiliary view will be on the* left of the button*/RIGHT("right", 2);static HorizontalAlignment DEFAULT = CENTER;static HorizontalAlignment fromInt(int enumValue) {for (HorizontalAlignment horizontalAlignment : values()) {if (horizontalAlignment.getValue() == enumValue) {return horizontalAlignment;}}return null;}private String stringValue;private int intValue;private HorizontalAlignment(String stringValue, int value) {this.stringValue = stringValue;this.intValue = value;}@Overridepublic String toString() {return stringValue;}private int getValue() {return intValue;}}/*** Encapsulates the valid values for the facebook:auxiliary_view_position attribute for a LikeView.*/public enum AuxiliaryViewPosition {/*** Setting the attribute to this value will put the social-sentence or box-count below the like button.* If the facebook:style is set to BUTTON, then this has no effect.** This is the default value*/BOTTOM("bottom", 0),/*** Setting the attribute to this value will put the social-sentence or box-count inline with the like button.* The auxiliary view will be to the left of the button if the facebook:horizontal_alignment is set to RIGHT.* In all other cases, it will be to the right of the button.* If the facebook:style is set to BUTTON, then this has no effect.*/INLINE("inline", 1),/*** Setting the attribute to this value will put the social-sentence or box-count above the like button.* If the facebook:style is set to BUTTON, then this has no effect.*/TOP("top", 2);static AuxiliaryViewPosition DEFAULT = BOTTOM;static AuxiliaryViewPosition fromInt(int enumValue) {for (AuxiliaryViewPosition auxViewPosition : values()) {if (auxViewPosition.getValue() == enumValue) {return auxViewPosition;}}return null;}private String stringValue;private int intValue;private AuxiliaryViewPosition(String stringValue, int value) {this.stringValue = stringValue;this.intValue = value;}@Overridepublic String toString() {return stringValue;}private int getValue() {return intValue;}}private static final int NO_FOREGROUND_COLOR = -1;private String objectId;private LinearLayout containerView;private LikeButton likeButton;private LikeBoxCountView likeBoxCountView;private TextView socialSentenceView;private LikeActionController likeActionController;private OnErrorListener onErrorListener;private BroadcastReceiver broadcastReceiver;private LikeActionControllerCreationCallback creationCallback;private Style likeViewStyle = Style.DEFAULT;private HorizontalAlignment horizontalAlignment = HorizontalAlignment.DEFAULT;private AuxiliaryViewPosition auxiliaryViewPosition = AuxiliaryViewPosition.DEFAULT;private int foregroundColor = NO_FOREGROUND_COLOR;private int edgePadding;private int internalPadding;/*** If your app does not use UiLifeCycleHelper, then you must call this method in the calling activity's* onActivityResult method, to process any pending like actions, where tapping the button had resulted in* the Like dialog being shown in the Facebook application.** @param context Hosting context* @param requestCode From the originating call to onActivityResult* @param resultCode From the originating call to onActivityResult* @param data From the originating call to onActivityResult* @return Indication of whether the Intent was handled*/public static boolean handleOnActivityResult(Context context,int requestCode,int resultCode,Intent data) {return LikeActionController.handleOnActivityResult(context, requestCode, resultCode, data);}/*** Constructor** @param context Context for this View*/public LikeView(Context context) {super(context);initialize(context);}/*** Constructor** @param context Context for this View* @param attrs AttributeSet for this View.*/public LikeView(Context context, AttributeSet attrs) {super(context, attrs);parseAttributes(attrs);initialize(context);}/*** Sets the associated object for this LikeView. Can be changed during runtime.* @param objectId Object Id*/public void setObjectId(String objectId) {objectId = Utility.coerceValueIfNullOrEmpty(objectId, null);if (!Utility.areObjectsEqual(objectId, this.objectId)) {setObjectIdForced(objectId);updateLikeStateAndLayout();}}/*** Sets the facebook:style for this LikeView. Can be changed during runtime.* @param likeViewStyle Should be either LikeView.STANDARD, LikeView.BUTTON or LikeView.BOX_COUNT*/public void setLikeViewStyle(Style likeViewStyle) {likeViewStyle = likeViewStyle != null ? likeViewStyle : Style.DEFAULT;if (this.likeViewStyle != likeViewStyle) {this.likeViewStyle = likeViewStyle;updateLayout();}}/*** Sets the facebook:auxiliary_view_position for this LikeView. Can be changed during runtime.* @param auxiliaryViewPosition Should be either LikeView.TOP, LikeView.INLINE or LikeView.BOTTOM*/public void setAuxiliaryViewPosition(AuxiliaryViewPosition auxiliaryViewPosition) {auxiliaryViewPosition = auxiliaryViewPosition != null ? auxiliaryViewPosition : AuxiliaryViewPosition.DEFAULT;if (this.auxiliaryViewPosition != auxiliaryViewPosition) {this.auxiliaryViewPosition = auxiliaryViewPosition;updateLayout();}}/*** Sets the facebook:horizontal_alignment for this LikeView. Can be changed during runtime.* @param horizontalAlignment Should be either LikeView.LEFT, LikeView.CENTER or LikeView.RIGHT*/public void setHorizontalAlignment(HorizontalAlignment horizontalAlignment) {horizontalAlignment = horizontalAlignment != null ? horizontalAlignment : HorizontalAlignment.DEFAULT;if (this.horizontalAlignment != horizontalAlignment) {this.horizontalAlignment = horizontalAlignment;updateLayout();}}/*** Sets the facebook:foreground_color for this LikeView. Can be changed during runtime.* The color is only used for the social sentence text.* @param foregroundColor And valid android.graphics.Color value.*/public void setForegroundColor(int foregroundColor) {if (this.foregroundColor != foregroundColor) {socialSentenceView.setTextColor(foregroundColor);}}/*** Sets an OnErrorListener for this instance of LikeView to call into when* certain exceptions occur.** @param onErrorListener The listener object to set*/public void setOnErrorListener(OnErrorListener onErrorListener) {this.onErrorListener = onErrorListener;}/*** Returns the current OnErrorListener for this instance of LikeView.** @return The OnErrorListener*/public OnErrorListener getOnErrorListener() {return onErrorListener;}@Overrideprotected void onDetachedFromWindow() {// Disassociate from the objectsetObjectId(null);super.onDetachedFromWindow();}private void parseAttributes(AttributeSet attrs) {if (attrs == null || getContext() == null) {return;}TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.com_facebook_like_view);if (a == null) {return;}objectId = Utility.coerceValueIfNullOrEmpty(a.getString(R.styleable.com_facebook_like_view_object_id), null);likeViewStyle = Style.fromInt(a.getInt(R.styleable.com_facebook_like_view_style,Style.DEFAULT.getValue()));if (likeViewStyle == null) {throw new IllegalArgumentException("Unsupported value for LikeView 'style'");}auxiliaryViewPosition = AuxiliaryViewPosition.fromInt(a.getInt(R.styleable.com_facebook_like_view_auxiliary_view_position,AuxiliaryViewPosition.DEFAULT.getValue()));if (auxiliaryViewPosition == null) {throw new IllegalArgumentException("Unsupported value for LikeView 'auxiliary_view_position'");}horizontalAlignment = HorizontalAlignment.fromInt(a.getInt(R.styleable.com_facebook_like_view_horizontal_alignment,HorizontalAlignment.DEFAULT.getValue()));if (horizontalAlignment == null) {throw new IllegalArgumentException("Unsupported value for LikeView 'horizontal_alignment'");}foregroundColor = a.getColor(R.styleable.com_facebook_like_view_foreground_color, NO_FOREGROUND_COLOR);a.recycle();}// If attributes were present, parseAttributes MUST be called before initialize() to ensure proper behaviorprivate void initialize(Context context) {edgePadding = getResources().getDimensionPixelSize(R.dimen.com_facebook_likeview_edge_padding);internalPadding = getResources().getDimensionPixelSize(R.dimen.com_facebook_likeview_internal_padding);if (foregroundColor == NO_FOREGROUND_COLOR) {foregroundColor = getResources().getColor(R.color.com_facebook_likeview_text_color);}setBackgroundColor(Color.TRANSPARENT);containerView = new LinearLayout(context);LayoutParams containerViewLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);containerView.setLayoutParams(containerViewLayoutParams);initializeLikeButton(context);initializeSocialSentenceView(context);initializeLikeCountView(context);containerView.addView(likeButton);containerView.addView(socialSentenceView);containerView.addView(likeBoxCountView);addView(containerView);setObjectIdForced(this.objectId);updateLikeStateAndLayout();}private void initializeLikeButton(Context context) {likeButton = new LikeButton(context,likeActionController != null ? likeActionController.isObjectLiked() : false);likeButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {toggleLike();}});LinearLayout.LayoutParams buttonLayout = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);likeButton.setLayoutParams(buttonLayout);}private void initializeSocialSentenceView(Context context) {socialSentenceView = new TextView(context);socialSentenceView.setTextSize(TypedValue.COMPLEX_UNIT_PX,getResources().getDimension(R.dimen.com_facebook_likeview_text_size));socialSentenceView.setMaxLines(2);socialSentenceView.setTextColor(foregroundColor);socialSentenceView.setGravity(Gravity.CENTER);LinearLayout.LayoutParams socialSentenceViewLayout = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.MATCH_PARENT);socialSentenceView.setLayoutParams(socialSentenceViewLayout);}private void initializeLikeCountView(Context context) {likeBoxCountView = new LikeBoxCountView(context);LinearLayout.LayoutParams likeCountViewLayout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);likeBoxCountView.setLayoutParams(likeCountViewLayout);}private void toggleLike() {if (likeActionController != null) {Activity activity = (Activity)getContext();likeActionController.toggleLike(activity, getAnalyticsParameters());}}private Bundle getAnalyticsParameters() {Bundle params = new Bundle();params.putString(AnalyticsEvents.PARAMETER_LIKE_VIEW_STYLE, likeViewStyle.toString());params.putString(AnalyticsEvents.PARAMETER_LIKE_VIEW_AUXILIARY_POSITION, auxiliaryViewPosition.toString());params.putString(AnalyticsEvents.PARAMETER_LIKE_VIEW_HORIZONTAL_ALIGNMENT, horizontalAlignment.toString());params.putString(AnalyticsEvents.PARAMETER_LIKE_VIEW_OBJECT_ID, Utility.coerceValueIfNullOrEmpty(objectId, ""));return params;}private void setObjectIdForced(String newObjectId) {tearDownObjectAssociations();objectId = newObjectId;if (Utility.isNullOrEmpty(newObjectId)) {return;}creationCallback = new LikeActionControllerCreationCallback();LikeActionController.getControllerForObjectId(getContext(),newObjectId,creationCallback);}private void associateWithLikeActionController(LikeActionController likeActionController) {this.likeActionController = likeActionController;this.broadcastReceiver = new LikeControllerBroadcastReceiver();LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(getContext());// add the broadcast receiverIntentFilter filter = new IntentFilter();filter.addAction(LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_UPDATED);filter.addAction(LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_DID_ERROR);filter.addAction(LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_DID_RESET);localBroadcastManager.registerReceiver(broadcastReceiver, filter);}private void tearDownObjectAssociations() {if (broadcastReceiver != null) {LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(getContext());localBroadcastManager.unregisterReceiver(broadcastReceiver);broadcastReceiver = null;}// If we were already waiting on a controller to be given back, make sure we aren't waiting anymore.// Otherwise when that controller is given back to the callback, it will go and register a broadcast receiver// for it.if (creationCallback != null) {creationCallback.cancel();creationCallback = null;}likeActionController = null;}private void updateLikeStateAndLayout() {if (likeActionController == null) {likeButton.setLikeState(false);socialSentenceView.setText(null);likeBoxCountView.setText(null);} else {likeButton.setLikeState(likeActionController.isObjectLiked());socialSentenceView.setText(likeActionController.getSocialSentence());likeBoxCountView.setText(likeActionController.getLikeCountString());}updateLayout();}private void updateLayout() {// Make sure the container is horizontally aligned according to specifications.LayoutParams containerViewLayoutParams = (LayoutParams)containerView.getLayoutParams();LinearLayout.LayoutParams buttonLayoutParams = (LinearLayout.LayoutParams)likeButton.getLayoutParams();int viewGravity =horizontalAlignment == HorizontalAlignment.LEFT ? Gravity.LEFT :horizontalAlignment == HorizontalAlignment.CENTER ? Gravity.CENTER_HORIZONTAL : Gravity.RIGHT;containerViewLayoutParams.gravity = viewGravity | Gravity.TOP;buttonLayoutParams.gravity = viewGravity;// Choose the right auxiliary view to make visible.socialSentenceView.setVisibility(GONE);likeBoxCountView.setVisibility(GONE);View auxView;if (likeViewStyle == Style.STANDARD &&likeActionController != null &&!Utility.isNullOrEmpty(likeActionController.getSocialSentence())) {auxView = socialSentenceView;} else if (likeViewStyle == Style.BOX_COUNT &&likeActionController != null &&!Utility.isNullOrEmpty(likeActionController.getLikeCountString())) {updateBoxCountCaretPosition();auxView = likeBoxCountView;} else {// No more work to be done.return;}auxView.setVisibility(VISIBLE);// Now position the auxiliary view properlyLinearLayout.LayoutParams auxViewLayoutParams = (LinearLayout.LayoutParams)auxView.getLayoutParams();auxViewLayoutParams.gravity = viewGravity;containerView.setOrientation(auxiliaryViewPosition == AuxiliaryViewPosition.INLINE ?LinearLayout.HORIZONTAL :LinearLayout.VERTICAL);if (auxiliaryViewPosition == AuxiliaryViewPosition.TOP ||(auxiliaryViewPosition == AuxiliaryViewPosition.INLINE &&horizontalAlignment == HorizontalAlignment.RIGHT)) {// Button comes after the auxiliary view. Make sure it is at the endcontainerView.removeView(likeButton);containerView.addView(likeButton);} else {// In all other cases, the button comes firstcontainerView.removeView(auxView);containerView.addView(auxView);}switch (auxiliaryViewPosition) {case TOP:auxView.setPadding(edgePadding, edgePadding, edgePadding, internalPadding);break;case BOTTOM:auxView.setPadding(edgePadding, internalPadding, edgePadding, edgePadding);break;case INLINE:if (horizontalAlignment == HorizontalAlignment.RIGHT) {auxView.setPadding(edgePadding, edgePadding, internalPadding, edgePadding);} else {auxView.setPadding(internalPadding, edgePadding, edgePadding, edgePadding);}break;}}private void updateBoxCountCaretPosition() {switch (auxiliaryViewPosition) {case TOP:likeBoxCountView.setCaretPosition(LikeBoxCountView.LikeBoxCountViewCaretPosition.BOTTOM);break;case BOTTOM:likeBoxCountView.setCaretPosition(LikeBoxCountView.LikeBoxCountViewCaretPosition.TOP);break;case INLINE:likeBoxCountView.setCaretPosition(horizontalAlignment == HorizontalAlignment.RIGHT ?LikeBoxCountView.LikeBoxCountViewCaretPosition.RIGHT :LikeBoxCountView.LikeBoxCountViewCaretPosition.LEFT);break;}}/*** Callback interface that will be called when a network or other error is encountered* while logging in.*/public interface OnErrorListener {/*** Called when a network or other error is encountered.* @param errorBundle a FacebookException representing the error that was encountered.*/void onError(Bundle errorBundle);}private class LikeControllerBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String intentAction = intent.getAction();Bundle extras = intent.getExtras();boolean shouldRespond = true;if (extras != null) {// See if an Id was set in the broadcast Intent. If it was, treat it as a filter.String broadcastObjectId = extras.getString(LikeActionController.ACTION_OBJECT_ID_KEY);shouldRespond = Utility.isNullOrEmpty(broadcastObjectId) ||Utility.areObjectsEqual(objectId, broadcastObjectId);}if (!shouldRespond) {return;}if (LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_UPDATED.equals(intentAction)) {updateLikeStateAndLayout();} else if (LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_DID_ERROR.equals(intentAction)) {if (onErrorListener != null) {onErrorListener.onError(extras);}} else if (LikeActionController.ACTION_LIKE_ACTION_CONTROLLER_DID_RESET.equals(intentAction)) {// This will recreate the controller and associated objectssetObjectIdForced(objectId);updateLikeStateAndLayout();}}}private class LikeActionControllerCreationCallback implements LikeActionController.CreationCallback {private boolean isCancelled;public void cancel() {isCancelled = true;}@Overridepublic void onComplete(LikeActionController likeActionController) {if (isCancelled) {return;}associateWithLikeActionController(likeActionController);updateLikeStateAndLayout();LikeView.this.creationCallback = null;}}}