| /* |
| * Copyright 2020 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.car.calendar; |
| |
| import android.Manifest; |
| import android.annotation.ColorInt; |
| import android.graphics.Paint; |
| import android.graphics.Typeface; |
| import android.graphics.drawable.InsetDrawable; |
| import android.graphics.drawable.ShapeDrawable; |
| import android.graphics.drawable.shapes.OvalShape; |
| import android.text.SpannableString; |
| import android.text.style.ForegroundColorSpan; |
| import android.text.style.ImageSpan; |
| import android.text.style.StyleSpan; |
| import android.view.LayoutInflater; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import androidx.annotation.DrawableRes; |
| import androidx.core.content.ContextCompat; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import com.android.car.calendar.common.CalendarFormatter; |
| import com.android.car.calendar.common.Dialer; |
| import com.android.car.calendar.common.Event; |
| import com.android.car.calendar.common.Navigator; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Lists; |
| |
| import javax.annotation.Nullable; |
| |
| /** An item in the calendar list view that shows a single event. */ |
| class EventCalendarItem implements CalendarItem { |
| private final Event mEvent; |
| private final CalendarFormatter mFormatter; |
| private final Navigator mNavigator; |
| private final Dialer mDialer; |
| private final CarCalendarActivity mCarCalendarActivity; |
| |
| EventCalendarItem( |
| Event event, |
| CalendarFormatter formatter, |
| Navigator navigator, |
| Dialer dialer, |
| CarCalendarActivity carCalendarActivity) { |
| mEvent = event; |
| mFormatter = formatter; |
| mNavigator = navigator; |
| mDialer = dialer; |
| mCarCalendarActivity = carCalendarActivity; |
| } |
| |
| @Override |
| public Type getType() { |
| return Type.EVENT; |
| } |
| |
| @Override |
| public void bind(RecyclerView.ViewHolder holder) { |
| EventViewHolder eventViewHolder = (EventViewHolder) holder; |
| |
| EventAction primaryAction; |
| if (!Strings.isNullOrEmpty(mEvent.getLocation())) { |
| primaryAction = |
| new EventAction( |
| R.drawable.ic_navigation_gm2_24px, |
| mEvent.getLocation(), |
| (view) -> mNavigator.navigate(mEvent.getLocation())); |
| } else { |
| primaryAction = |
| new EventAction( |
| R.drawable.ic_navigation_gm2_24px, /* descriptionText */ |
| null, /* onClickHandler */ |
| null); |
| } |
| |
| EventAction secondaryAction; |
| Dialer.NumberAndAccess numberAndAccess = mEvent.getNumberAndAccess(); |
| if (numberAndAccess != null) { |
| String dialDescriptionText; |
| if (numberAndAccess.getAccess() != null) { |
| dialDescriptionText = |
| mCarCalendarActivity.getString( |
| R.string.phone_number_with_pin, |
| numberAndAccess.getNumber(), |
| numberAndAccess.getAccess()); |
| |
| } else { |
| dialDescriptionText = |
| mCarCalendarActivity.getString( |
| R.string.phone_number, numberAndAccess.getNumber()); |
| } |
| secondaryAction = |
| new EventAction( |
| R.drawable.ic_phone_gm2_24px, |
| dialDescriptionText, |
| (view) -> dial(numberAndAccess)); |
| } else { |
| secondaryAction = |
| new EventAction( |
| R.drawable.ic_phone_gm2_24px, |
| /* descriptionText */ null, |
| /* onClickListener= */ null); |
| } |
| |
| String timeRangeText; |
| if (!mEvent.isAllDay()) { |
| timeRangeText = |
| mFormatter.getTimeRangeText(mEvent.getStartInstant(), mEvent.getEndInstant()); |
| } else { |
| timeRangeText = mCarCalendarActivity.getString(R.string.all_day_event); |
| } |
| eventViewHolder.update( |
| timeRangeText, |
| mEvent.getTitle(), |
| mEvent.getCalendarDetails().getColor(), |
| mEvent.getCalendarDetails().getName(), |
| primaryAction, |
| secondaryAction, |
| mEvent.getStatus()); |
| } |
| |
| private void dial(Dialer.NumberAndAccess numberAndAccess) { |
| mCarCalendarActivity.runWithPermission( |
| Manifest.permission.CALL_PHONE, |
| () -> { |
| if (!mDialer.dial(numberAndAccess)) { |
| Toast.makeText(mCarCalendarActivity, R.string.no_dialler, Toast.LENGTH_LONG) |
| .show(); |
| } |
| }); |
| } |
| |
| private static class EventAction { |
| @DrawableRes private final int mIconResourceId; |
| @Nullable private final String mDescriptionText; |
| @Nullable private final OnClickListener mOnClickListener; |
| |
| private EventAction( |
| int iconResourceId, |
| @Nullable String descriptionText, |
| @Nullable OnClickListener onClickListener) { |
| this.mIconResourceId = iconResourceId; |
| this.mDescriptionText = descriptionText; |
| this.mOnClickListener = onClickListener; |
| } |
| } |
| |
| static class EventViewHolder extends RecyclerView.ViewHolder { |
| private static final String FIELD_SEPARATOR = ", "; |
| private static final Joiner JOINER = Joiner.on(FIELD_SEPARATOR).skipNulls(); |
| |
| private final TextView mTitleView; |
| private final TextView mDescriptionView; |
| private final DrawableStateImageButton mPrimaryActionButton; |
| private final DrawableStateImageButton mSecondaryActionButton; |
| private final int mCalendarIndicatorSize; |
| private final int mCalendarIndicatorPadding; |
| @ColorInt private final int mTimeTextColor; |
| |
| EventViewHolder(ViewGroup parent) { |
| super( |
| LayoutInflater.from(parent.getContext()) |
| .inflate(R.layout.event_item, parent, /* attachToRoot= */ false)); |
| mTitleView = itemView.findViewById(R.id.event_title); |
| mDescriptionView = itemView.findViewById(R.id.description_text); |
| mPrimaryActionButton = itemView.findViewById(R.id.primary_action_button); |
| mSecondaryActionButton = itemView.findViewById(R.id.secondary_action_button); |
| mCalendarIndicatorSize = |
| (int) parent.getResources().getDimension(R.dimen.car_calendar_indicator_width); |
| mCalendarIndicatorPadding = |
| (int) parent.getResources().getDimension(R.dimen.car_ui_padding_1); |
| mTimeTextColor = |
| ContextCompat.getColor(parent.getContext(), R.color.car_ui_text_color_primary); |
| } |
| |
| void update( |
| String timeRangeText, |
| String title, |
| @ColorInt int calendarColor, |
| String calendarName, |
| EventAction primaryAction, |
| EventAction secondaryAction, |
| Event.Status status) { |
| |
| mTitleView.setText(title); |
| |
| String detailText = null; |
| if (primaryAction.mDescriptionText != null) { |
| detailText = primaryAction.mDescriptionText; |
| } else if (secondaryAction.mDescriptionText != null) { |
| detailText = secondaryAction.mDescriptionText; |
| } |
| SpannableString descriptionSpannable = |
| createDescriptionSpannable( |
| calendarColor, calendarName, timeRangeText, detailText); |
| |
| mDescriptionView.setText(descriptionSpannable); |
| |
| // Strike-through all text fields when the event was declined. |
| setTextFlags( |
| Paint.STRIKE_THRU_TEXT_FLAG, |
| /* add= */ status.equals(Event.Status.DECLINED), |
| mTitleView, |
| mDescriptionView); |
| |
| mPrimaryActionButton.setImageResource(primaryAction.mIconResourceId); |
| if (primaryAction.mOnClickListener != null) { |
| mPrimaryActionButton.setEnabled(true); |
| mPrimaryActionButton.setContentDescription(primaryAction.mDescriptionText); |
| mPrimaryActionButton.setOnClickListener(primaryAction.mOnClickListener); |
| } else { |
| mPrimaryActionButton.setEnabled(false); |
| mPrimaryActionButton.setContentDescription(null); |
| mPrimaryActionButton.setOnClickListener(null); |
| } |
| |
| mSecondaryActionButton.setImageResource(secondaryAction.mIconResourceId); |
| if (secondaryAction.mOnClickListener != null) { |
| mSecondaryActionButton.setEnabled(true); |
| mSecondaryActionButton.setContentDescription(secondaryAction.mDescriptionText); |
| mSecondaryActionButton.setOnClickListener(secondaryAction.mOnClickListener); |
| } else { |
| mSecondaryActionButton.setEnabled(false); |
| mSecondaryActionButton.setContentDescription(null); |
| mSecondaryActionButton.setOnClickListener(null); |
| } |
| } |
| |
| private SpannableString createDescriptionSpannable( |
| @ColorInt int calendarColor, |
| String calendarName, |
| String timeRangeText, |
| @Nullable String detailText) { |
| ShapeDrawable calendarIndicatorDrawable = new ShapeDrawable(new OvalShape()); |
| calendarIndicatorDrawable.getPaint().setColor(calendarColor); |
| |
| calendarIndicatorDrawable.setBounds( |
| /* left= */ 0, |
| /* top= */ 0, |
| /* right= */ mCalendarIndicatorSize, |
| /* bottom= */ mCalendarIndicatorSize); |
| |
| // Add padding to the right of the image to separate it from the text. |
| InsetDrawable insetDrawable = |
| new InsetDrawable( |
| calendarIndicatorDrawable, |
| /* insetLeft= */ 0, |
| /* insetTop= */ 0, |
| /* insetRight= */ mCalendarIndicatorPadding, |
| /* insetBottom= */ 0); |
| |
| insetDrawable.setBounds( |
| /* left= */ 0, |
| /* top= */ 0, |
| /* right= */ mCalendarIndicatorSize + mCalendarIndicatorPadding, |
| /* bottom= */ mCalendarIndicatorSize); |
| |
| String descriptionText = |
| JOINER.join(Lists.newArrayList(calendarName, timeRangeText, detailText)); |
| SpannableString descriptionSpannable = new SpannableString(descriptionText); |
| ImageSpan calendarIndicatorSpan = |
| new ImageSpan(insetDrawable, ImageSpan.ALIGN_BASELINE); |
| int calendarNameEnd = calendarName.length() + FIELD_SEPARATOR.length(); |
| descriptionSpannable.setSpan( |
| calendarIndicatorSpan, /* start= */ 0, calendarNameEnd, /* flags= */ 0); |
| int timeEnd = calendarNameEnd + timeRangeText.length(); |
| descriptionSpannable.setSpan( |
| new StyleSpan(Typeface.BOLD), calendarNameEnd, timeEnd, /* flags= */ 0); |
| descriptionSpannable.setSpan( |
| new ForegroundColorSpan(mTimeTextColor), |
| calendarNameEnd, |
| timeEnd, |
| /* flags= */ 0); |
| return descriptionSpannable; |
| } |
| |
| /** |
| * Set paint flags on the given text views. |
| * |
| * @param flags The combined {@link Paint} flags to set or unset. |
| * @param set Set the flags if true, otherwise unset. |
| * @param views The views to apply the flags to. |
| */ |
| private void setTextFlags(int flags, boolean set, TextView... views) { |
| for (TextView view : views) { |
| if (set) { |
| view.setPaintFlags(view.getPaintFlags() | flags); |
| } else { |
| view.setPaintFlags(view.getPaintFlags() & ~flags); |
| } |
| } |
| } |
| } |
| } |