/* * Copyright (c) 2016 Uber Technologies, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.uber.sdk.android.rides; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import com.uber.sdk.android.core.UberButton; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.UberStyle; import com.uber.sdk.android.rides.internal.RideRequestButtonController; import com.uber.sdk.android.rides.internal.RideRequestButtonView; import com.uber.sdk.rides.client.Session; import com.uber.sdk.rides.client.SessionConfiguration; import com.uber.sdk.rides.client.model.PriceEstimate; import com.uber.sdk.rides.client.model.TimeEstimate; import java.util.concurrent.TimeUnit; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; /** * An Uber styled button to request rides with specific {@link RideParameters}. Default {@link RideParameters} is * set to a pickup of the device's location. Requires a client ID to function. */ public class RideRequestButton extends FrameLayout implements RideRequestButtonView { private static final @StyleRes int[] STYLES = {R.style.UberButton, R.style.UberButton_White}; private static final String USER_AGENT_BUTTON = "rides-android-v0.6.1-button"; private RideRequestBehavior rideRequestBehavior; @NonNull private RideParameters rideParameters = new RideParameters.Builder().build(); private UberButton requestButton; private TextView timeEstimateView; private TextView priceEstimateView; private RideRequestButtonController controller; private Session session; private RideRequestButtonCallback callback; public RideRequestButton(Context context) { this(context, null); } public RideRequestButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RideRequestButton(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, R.style.UberButton_Login); } @TargetApi(21) public RideRequestButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); final UberStyle uberStyle = UberStyle.getStyleFromAttribute(context, attrs, defStyleRes, R.styleable.RideRequestButton, R.styleable.RideRequestButton_ub__style); init(context, attrs, defStyleAttr, uberStyle); } private void init(Context context, AttributeSet attrs, int defStyleAttr, UberStyle uberStyle) { @StyleRes int styleRes = STYLES[uberStyle.getValue()]; inflate(context, R.layout.ub__ride_request_button, this); requestButton = (UberButton) findViewById(R.id.request_button); timeEstimateView = (TextView) findViewById(R.id.time_estimate); priceEstimateView = (TextView) findViewById(R.id.price_estimate); setBackgroundAttributes(context, attrs, defStyleAttr, styleRes); setPaddingAttributes(context, attrs, defStyleAttr, styleRes); setTextAttributes(context, attrs, defStyleAttr, styleRes); showDefaultView(); setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { rideParameters.setUserAgent(USER_AGENT_BUTTON); if (rideRequestBehavior == null) { final SessionConfiguration config; if (session != null) { config = session.getAuthenticator().getSessionConfiguration(); } else { config = UberSdk.getDefaultSessionConfiguration(); } rideRequestBehavior = new RequestDeeplinkBehavior(config); } rideRequestBehavior.requestRide(getContext(), rideParameters); } }); } /** * Sets the {@link RideParameters} that will be used to request a ride when the button is clicked. If not set will * use default RideParameters behavior. * * @return this instance of {@link RideRequestButton} */ public RideRequestButton setRideParameters(@NonNull RideParameters rideParameters) { this.rideParameters = rideParameters; return this; } /** * Sets how the request button should act for button actions. * * @param requestBehavior an object that implements {@link RideRequestBehavior} * @return this instance of {@link RideRequestButton} */ public RideRequestButton setRequestBehavior(@NonNull RideRequestBehavior requestBehavior) { rideRequestBehavior = requestBehavior; return this; } /** * Set {@link RideRequestButtonCallback}. This is optional but it is recommended to supply one * for error handling. * * @param callback to be notified on Success or Failure * @return this instance of {@link RideRequestButton} */ public RideRequestButton setCallback(@NonNull RideRequestButtonCallback callback) { this.callback = callback; return this; } /** * Retrieve estimates from the server and updates the button accordingly. Requires: * 1. {@link #setSession(Session)} * 2. {@link #setRideParameters(RideParameters)}, with product_id, pick_up_latitude and pick_up_longitude are required to get Time Estimate. drop_off_latitude and drop_off_longitude are only required if Price Estimate is desired. * 3. {@link #setCallback(RideRequestButtonCallback)}, this is although optional but recommended to handle errors and track events. */ public void loadRideInformation() { checkNotNull(session, "ServerToken is empty. Have you called setServerToken?"); getOrCreateController().loadRideInformation(rideParameters); } /** * Set {@link Session} * * @param session to be used for request signing * @return this instance of {@link RideRequestButton} */ public RideRequestButton setSession(@NonNull Session session) { this.session = session; return this; } private void setBackgroundAttributes( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { int attrsResources[] = { android.R.attr.background, }; TypedArray backgroundAttributes = context.getTheme().obtainStyledAttributes( attrs, attrsResources, defStyleAttr, defStyleRes); try { applyBackgroundResource(backgroundAttributes); } finally { backgroundAttributes.recycle(); } } private void setTextAttributes( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { int attrsResources[] = { android.R.attr.textColor, android.R.attr.gravity, android.R.attr.textSize, android.R.attr.textStyle, android.R.attr.text }; TypedArray textAttributes = context.getTheme().obtainStyledAttributes( attrs, attrsResources, defStyleAttr, defStyleRes); try { applyTextAttributes(priceEstimateView, textAttributes); applyTextAttributes(timeEstimateView, textAttributes); applyTextAttributes(requestButton, textAttributes); } finally { textAttributes.recycle(); } } @SuppressLint("ResourceType") private void applyTextAttributes(TextView textView, TypedArray textAttributes) { textView.setTextColor(textAttributes.getColor(0, Color.WHITE)); textView.setTypeface(Typeface.defaultFromStyle(textAttributes.getInt(3, Typeface.NORMAL))); String text = textAttributes.getString(4); if (text != null) { textView.setText(textAttributes.getString(4)); } } @SuppressLint("ResourceType") private void setPaddingAttributes( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { int attrsResources[] = { android.R.attr.padding, android.R.attr.paddingLeft, android.R.attr.paddingTop, android.R.attr.paddingRight, android.R.attr.paddingBottom, }; TypedArray paddingAttributes = context.getTheme().obtainStyledAttributes( attrs, attrsResources, defStyleAttr, defStyleRes); try { int padding = paddingAttributes.getDimensionPixelOffset(0, 0); int paddingLeft = paddingAttributes.getDimensionPixelSize(1, 0); paddingLeft = paddingLeft == 0 ? padding : paddingLeft; int paddingTop = paddingAttributes.getDimensionPixelSize(2, 0); paddingTop = paddingTop == 0 ? padding : paddingTop; int paddingRight = paddingAttributes.getDimensionPixelSize(3, 0); paddingRight = paddingRight == 0 ? padding : paddingRight; int paddingBottom = paddingAttributes.getDimensionPixelSize(4, 0); paddingBottom = paddingBottom == 0 ? padding : paddingBottom; setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } finally { paddingAttributes.recycle(); } } private void applyBackgroundResource(TypedArray backgroundAttributes) { int color = Color.BLACK; if (backgroundAttributes.hasValue(0)) { int backgroundResource = backgroundAttributes.getResourceId(0, 0); if (backgroundResource != 0) { setBackgroundResource(backgroundResource); for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); if (!(childView instanceof LinearLayout)) { childView.setBackgroundResource(backgroundResource); } } return; } else { color = backgroundAttributes.getColor(0, Color.BLACK); } } setBackgroundColor(color); for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); childView.setBackgroundColor(color); } } private synchronized RideRequestButtonController getOrCreateController() { if (controller == null) { checkNotNull(session, "Must set session using setSession."); controller = new RideRequestButtonController(this, session, callback); } return controller; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (controller != null) { controller.destroy(); } } @Override public void showDefaultView() { requestButton.setText(R.string.ub__ride_with_uber); priceEstimateView.setText(""); priceEstimateView.setVisibility(GONE); timeEstimateView.setText(""); timeEstimateView.setVisibility(GONE); } @Override public void showEstimate(@NonNull TimeEstimate timeEstimate) { requestButton.setText(R.string.ub__get_ride); timeEstimateView.setText(getResources().getString(R.string.ub__time_estimate, TimeUnit.SECONDS.toMinutes(timeEstimate.getEstimate()))); timeEstimateView.setVisibility(VISIBLE); } @Override public void showEstimate(@NonNull TimeEstimate timeEstimate, @NonNull PriceEstimate priceEstimate) { showEstimate(timeEstimate); priceEstimateView.setText(getResources().getString(R.string.ub__price_estimate, priceEstimate.getEstimate(), priceEstimate.getDisplayName())); priceEstimateView.setVisibility(VISIBLE); } }