Replace weekbutton with color and drawable lists

pull/618/head
Alex Baker 7 years ago
parent 8390f9ecb5
commit 580b9ce721

@ -1,87 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
* Copyright 2015 Vikram Kakkar
*
* 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.appeaser.sublimepickerlibrary.drawables;
import android.animation.TypeEvaluator;
import android.graphics.RectF;
/**
* This evaluator can be used to perform type interpolation between RectF values.
* It is a modified version of 'RectEvaluator'
*/
public class CRectFEvaluator implements TypeEvaluator<RectF> {
/**
* When null, a new Rect is returned on every evaluate call. When non-null,
* mRect will be modified and returned on every evaluate.
*/
private RectF mRectF;
/**
* Construct a RectEvaluator that returns a new Rect on every evaluate call.
* To avoid creating an object for each evaluate call,
* {@link CRectFEvaluator#CRectFEvaluator(RectF)} should be used
* whenever possible.
*/
public CRectFEvaluator() {
}
/**
* Constructs a RectEvaluator that modifies and returns <code>reuseRect</code>
* in #evaluate(float, android.graphics.RectF, android.graphics.Rect) calls.
* The value returned from
* #evaluate(float, android.graphics.RectF, android.graphics.Rect) should
* not be cached because it will change over time as the object is reused on each
* call.
*
* @param reuseRect A Rect to be modified and returned by evaluate.
*/
public CRectFEvaluator(RectF reuseRect) {
mRectF = reuseRect;
}
/**
* This function returns the result of linearly interpolating the start and
* end Rect values, with <code>fraction</code> representing the proportion
* between the start and end values. The calculation is a simple parametric
* calculation on each of the separate components in the Rect objects
* (left, top, right, and bottom).
* <p>If #CRectFEvaluator(android.graphics.Rect) was used to construct
* this RectEvaluator, the object returned will be the <code>reuseRect</code>
* passed into the constructor.</p>
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start Rect
* @param endValue The end Rect
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
@Override
public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
float left = startValue.left + (endValue.left - startValue.left) * fraction;
float top = startValue.top + (endValue.top - startValue.top) * fraction;
float right = startValue.right + (endValue.right - startValue.right) * fraction;
float bottom = startValue.bottom + (endValue.bottom - startValue.bottom) * fraction;
if (mRectF == null) {
return new RectF(left, top, right, bottom);
} else {
mRectF.set(left, top, right, bottom);
return mRectF;
}
}
}

@ -1,225 +0,0 @@
/*
* Copyright 2015 Vikram Kakkar
*
* 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.appeaser.sublimepickerlibrary.drawables;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.OvershootInterpolator;
/**
* Provides animated transition between 'on' and 'off' state.
* Used as background for 'WeekButton'.
*/
public class CheckableDrawable extends Drawable {
private final int ANIMATION_DURATION_EXPAND = 500, ANIMATION_DURATION_COLLAPSE = 400;
private int mMinAlpha, mMaxAlpha;
private Paint mPaint;
private AnimatorSet asTransition;
private final OvershootInterpolator mExpandInterpolator = new OvershootInterpolator();
private final AnticipateInterpolator mCollapseInterpolator = new AnticipateInterpolator();
private final CRectFEvaluator mRectEvaluator = new CRectFEvaluator();
private RectF mRectToDraw, mCollapsedRect, mExpandedRect;
private int mExpandedWidthHeight;
private boolean mChecked, mReady;
public CheckableDrawable(int color, boolean checked, int expandedWidthHeight) {
mChecked = checked;
mExpandedWidthHeight = expandedWidthHeight;
mMaxAlpha = Color.alpha(color);
// Todo: Provide an option to change this value
mMinAlpha = 0;
mRectToDraw = new RectF();
mExpandedRect = new RectF();
mCollapsedRect = new RectF();
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setAlpha(mMaxAlpha);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
}
// initialize dimensions
private void setDimens(int width, int height) {
mReady = true;
float expandedLeft = (width - mExpandedWidthHeight) / 2f;
float expandedTop = (height - mExpandedWidthHeight) / 2f;
float expandedRight = (width + mExpandedWidthHeight) / 2f;
float expandedBottom = (height + mExpandedWidthHeight) / 2f;
float collapsedLeft = width / 2f;
float collapsedTop = height / 2f;
float collapsedRight = width / 2f;
float collapsedBottom = height / 2f;
mCollapsedRect = new RectF(collapsedLeft, collapsedTop,
collapsedRight, collapsedBottom);
mExpandedRect = new RectF(expandedLeft, expandedTop,
expandedRight, expandedBottom);
reset();
}
// Called when 'WeekButton' checked state changes
public void setCheckedOnClick(boolean checked, final OnAnimationDone callback) {
mChecked = checked;
if (!mReady) {
invalidateSelf();
return;
}
reset();
onClick(callback);
}
private void onClick(final OnAnimationDone callback) {
animate(mChecked, callback);
}
private void cancelAnimationInTracks() {
if (asTransition != null && asTransition.isRunning()) {
asTransition.cancel();
}
}
// Set state without animation
public void setChecked(boolean checked) {
if (mChecked == checked)
return;
mChecked = checked;
reset();
}
private void reset() {
cancelAnimationInTracks();
if (mChecked) {
mRectToDraw.set(mExpandedRect);
} else {
mRectToDraw.set(mCollapsedRect);
}
invalidateSelf();
}
// Animate between 'on' & 'off' state
private void animate(boolean expand, final OnAnimationDone callback) {
RectF from = expand ? mCollapsedRect : mExpandedRect;
RectF to = expand ? mExpandedRect : mCollapsedRect;
mRectToDraw.set(from);
ObjectAnimator oaTransition = ObjectAnimator.ofObject(this,
"newRectBounds",
mRectEvaluator, from, to);
int duration = expand ?
ANIMATION_DURATION_EXPAND :
ANIMATION_DURATION_COLLAPSE;
oaTransition.setDuration(duration);
oaTransition.setInterpolator(expand ?
mExpandInterpolator :
mCollapseInterpolator);
ObjectAnimator oaAlpha = ObjectAnimator.ofInt(this,
"alpha",
expand ? mMinAlpha : mMaxAlpha,
expand ? mMaxAlpha : mMinAlpha);
oaAlpha.setDuration(duration);
asTransition = new AnimatorSet();
asTransition.playTogether(oaTransition, oaAlpha);
asTransition.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (callback != null) {
callback.animationIsDone();
}
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
if (callback != null) {
callback.animationHasBeenCancelled();
}
}
});
asTransition.start();
}
@Override
public void draw(Canvas canvas) {
if (!mReady) {
setDimens(getBounds().width(), getBounds().height());
return;
}
canvas.drawOval(mRectToDraw, mPaint);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
// ObjectAnimator property
@SuppressWarnings("unused")
public void setNewRectBounds(RectF newRectBounds) {
mRectToDraw = newRectBounds;
invalidateSelf();
}
// Callback
public interface OnAnimationDone {
void animationIsDone();
void animationHasBeenCancelled();
}
}

@ -1,109 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
* Copyright 2015 Vikram Kakkar
*
* 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.appeaser.sublimepickerlibrary.recurrencepicker;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ToggleButton;
import com.appeaser.sublimepickerlibrary.drawables.CheckableDrawable;
public class WeekButton extends ToggleButton {
private static int mDefaultTextColor, mCheckedTextColor;
// Drawable that provides animations between
// 'on' & 'off' states
private CheckableDrawable mDrawable;
// Flag to disable animation on state change
private boolean noAnimate = false;
public WeekButton(Context context) {
super(context);
}
public WeekButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WeekButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// Syncs state
private CheckableDrawable.OnAnimationDone mCallback = new CheckableDrawable.OnAnimationDone() {
@Override
public void animationIsDone() {
setTextColor(isChecked() ? mCheckedTextColor : mDefaultTextColor);
mDrawable.setChecked(isChecked());
}
@Override
public void animationHasBeenCancelled() {
setTextColor(isChecked() ? mCheckedTextColor : mDefaultTextColor);
mDrawable.setChecked(isChecked());
}
};
// Wrapper for 'setChecked(boolean)' that does not trigger
// state-animation
public void setCheckedNoAnimate(boolean checked) {
noAnimate = true;
setChecked(checked);
noAnimate = false;
}
@Override
public void setChecked(final boolean checked) {
super.setChecked(checked);
if (mDrawable != null) {
if (noAnimate) {
mDrawable.setChecked(checked);
setTextColor(isChecked() ? mCheckedTextColor : mDefaultTextColor);
} else {
// Reset text color for animation
// The correct state color will be
// set when animation is done or cancelled
setTextColor(mCheckedTextColor);
mDrawable.setCheckedOnClick(isChecked(), mCallback);
}
}
}
@Override
public void setBackgroundDrawable(Drawable d) {
super.setBackgroundDrawable(d);
if (d instanceof CheckableDrawable) {
mDrawable = (CheckableDrawable) d;
} else {
// Reset: in case setBackgroundDrawable
// is called more than once
mDrawable = null;
}
}
// State-dependent text-colors
public static void setStateColors(int defaultColor, int checkedColor) {
mDefaultTextColor = defaultColor;
mCheckedTextColor = checkedColor;
}
}

@ -5,6 +5,11 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -20,9 +25,8 @@ import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.appeaser.sublimepickerlibrary.drawables.CheckableDrawable;
import com.appeaser.sublimepickerlibrary.recurrencepicker.WeekButton;
import com.google.common.base.Strings;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
@ -39,8 +43,6 @@ import org.tasks.injection.ForActivity;
import org.tasks.injection.InjectingDialogFragment;
import org.tasks.locale.Locale;
import org.tasks.preferences.ResourceResolver;
import org.tasks.themes.Theme;
import org.tasks.themes.ThemeAccent;
import org.tasks.time.DateTime;
import java.text.DateFormatSymbols;
@ -95,18 +97,17 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
@Inject @ForActivity Context context;
@Inject DialogBuilder dialogBuilder;
@Inject Theme theme;
@Inject Locale locale;
@BindView(R.id.weekGroup) LinearLayout weekGroup1;
@BindView(R.id.weekGroup2) @Nullable LinearLayout weekGroup2;
@BindView(R.id.week_day_1) WeekButton day1;
@BindView(R.id.week_day_2) WeekButton day2;
@BindView(R.id.week_day_3) WeekButton day3;
@BindView(R.id.week_day_4) WeekButton day4;
@BindView(R.id.week_day_5) WeekButton day5;
@BindView(R.id.week_day_6) WeekButton day6;
@BindView(R.id.week_day_7) WeekButton day7;
@BindView(R.id.week_day_1) ToggleButton day1;
@BindView(R.id.week_day_2) ToggleButton day2;
@BindView(R.id.week_day_3) ToggleButton day3;
@BindView(R.id.week_day_4) ToggleButton day4;
@BindView(R.id.week_day_5) ToggleButton day5;
@BindView(R.id.week_day_6) ToggleButton day6;
@BindView(R.id.week_day_7) ToggleButton day7;
@BindView(R.id.month_group) RadioGroup monthGroup;
@BindView(R.id.repeat_monthly_same_day) RadioButton repeatMonthlySameDay;
@ -122,17 +123,18 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
private final List<String> repeatUntilOptions = new ArrayList<>();
private ArrayAdapter<String> repeatUntilAdapter;
private WeekButton[] weekButtons;
private ToggleButton[] weekButtons;
private RRule rrule;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
LayoutInflater inflater = LayoutInflater.from(context);
View dialogView = inflater.inflate(R.layout.control_set_repeat, null);
Bundle arguments = getArguments();
long dueDate = arguments.getLong(EXTRA_DATE, currentTimeMillis());
String rule = savedInstanceState == null
? arguments.getString(EXTRA_RRULE)
: savedInstanceState.getString(EXTRA_RRULE);
@ -155,7 +157,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
ButterKnife.bind(this, dialogView);
Calendar dayOfMonthCalendar = Calendar.getInstance(locale.getLocale());
dayOfMonthCalendar.setTimeInMillis(arguments.getLong(EXTRA_DATE, currentTimeMillis()));
dayOfMonthCalendar.setTimeInMillis(dueDate);
int dayOfWeekInMonth = dayOfMonthCalendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
int maxDayOfWeekInMonth = dayOfMonthCalendar.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH);
@ -234,49 +236,60 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
repeatUntilSpinner.setAdapter(repeatUntilAdapter);
updateRepeatUntilOptions();
weekButtons = new WeekButton[] { day1, day2, day3, day4, day5, day6, day7 };
int expandedWidthHeight = getResources()
.getDimensionPixelSize(R.dimen.week_button_state_on_circle_size);
int weekButtonUnselectedTextColor = getColor(context, R.color.text_primary);
int weekButtonSelectedTextColor = ResourceResolver.getData(context, R.attr.fab_text);
WeekButton.setStateColors(weekButtonUnselectedTextColor, weekButtonSelectedTextColor);
weekButtons = new ToggleButton[] { day1, day2, day3, day4, day5, day6, day7 };
// set up days of week
ThemeAccent accent = theme.getThemeAccent();
Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale());
dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeekCalendar.getFirstDayOfWeek());
WeekdayNum todayWeekday = new WeekdayNum(0, new DateTime(dueDate).getWeekday());
ColorStateList colorStateList = new ColorStateList(new int[][]{
new int[]{android.R.attr.state_checked},
new int[]{-android.R.attr.state_checked}
}, new int[]{
ResourceResolver.getData(context, R.attr.fab_text),
getColor(context, R.color.text_primary)
});
int inset = (int) context.getResources().getDimension(R.dimen.week_button_inset);
int accentColor = ResourceResolver.getData(context, R.attr.colorAccent);
for(int i = 0; i < 7; i++) {
int index = i;
GradientDrawable ovalDrawable = (GradientDrawable) context.getResources().getDrawable(R.drawable.week_day_button_oval).mutate();
ovalDrawable.setColor(accentColor);
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ovalDrawable});
layerDrawable.setLayerInset(0, inset, inset, inset, inset);
int dayOfWeek = dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK);
ToggleButton weekButton = weekButtons[i];
((StateListDrawable) weekButton.getBackground())
.addState(new int[] {android.R.attr.state_checked}, layerDrawable);
String text = shortWeekdays[dayOfWeek];
WeekdayNum weekdayNum = new WeekdayNum(0, calendarDayToWeekday(dayOfWeek));
WeekButton weekButton = weekButtons[i];
weekButton.setTag(weekdayNum);
weekButton.setBackgroundDrawable(new CheckableDrawable(accent.getAccentColor(), false, expandedWidthHeight));
weekButton.setTextColor(weekButtonUnselectedTextColor);
weekButton.setTextOff(text);
weekButton.setTextColor(colorStateList);
weekButton.setTextOn(text);
weekButton.setText(text);
weekButton.setTextOff(text);
weekButton.setTag(new WeekdayNum(0, calendarDayToWeekday(dayOfWeek)));
if (savedInstanceState == null) {
weekButton.setCheckedNoAnimate(rrule.getByDay().contains(weekdayNum));
weekButton.setChecked(rrule.getFreq() != WEEKLY || rrule.getByDay().isEmpty()
? todayWeekday.equals(weekButton.getTag())
: rrule.getByDay().contains(weekButton.getTag()));
}
dayOfWeekCalendar.add(Calendar.DATE, 1);
}
setCancelable(false);
return dialogBuilder.newDialog()
.setView(dialogView)
.setPositiveButton(android.R.string.ok, this::onRuleSelected)
.setNegativeButton(android.R.string.cancel, null)
.setOnCancelListener(DialogInterface::dismiss)
.show();
}
private void onRuleSelected(DialogInterface dialogInterface, int which) {
if (rrule.getFreq() == WEEKLY) {
List<WeekdayNum> checked = new ArrayList<>();
for (WeekButton weekButton : weekButtons) {
for (ToggleButton weekButton : weekButtons) {
if (weekButton.isChecked()) {
checked.add((WeekdayNum) weekButton.getTag());
}
@ -298,6 +311,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
rrule.setByDay(Collections.emptyList());
}
((CustomRecurrenceCallback) getTargetFragment()).onSelected(rrule);
dismiss();
}
private Weekday calendarDayToWeekday(int calendarDay) {
@ -320,15 +334,6 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
throw new RuntimeException("Invalid calendar day: " + calendarDay);
}
@Override
public void onResume() {
super.onResume();
for (WeekButton weekButton : weekButtons) {
weekButton.setCheckedNoAnimate(weekButton.isChecked());
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="@android:integer/config_shortAnimTime"
android:exitFadeDuration="@android:integer/config_shortAnimTime">
<item android:state_checked="false" android:drawable="@drawable/transparent_drawable" />
</selector>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="oval" xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/week_button_state_on_circle_size" />
</shape>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
** See the file "LICENSE" for the full license governing this code.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"

@ -1,22 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2015 Vikram Kakkar
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.
-->
<com.appeaser.sublimepickerlibrary.recurrencepicker.WeekButton
<?xml version="1.0" encoding="utf-8"?>
<ToggleButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="60dp"
android:layout_height="60dp"
android:textAllCaps="true"
android:textSize="12sp"
android:singleLine="true"
android:background="@drawable/week_button_bg"
android:gravity="center" />

@ -6,6 +6,8 @@
-->
<resources>
<drawable name="transparent_drawable">@android:color/transparent</drawable>
<color name="red_500">#f44336</color>
<color name="red_700">#d32f2f</color>
<color name="red_a400">#ff1744</color>

@ -23,5 +23,6 @@
<dimen name="elevation_padding">0dp</dimen>
<dimen name="widget_padding">10dp</dimen>
<dimen name="week_button_inset">6dp</dimen>
<dimen name="week_button_state_on_circle_size">48dp</dimen>
</resources>
Loading…
Cancel
Save