Merge branch 'feature/numerical' into dev

pull/605/head
Alinson S. Xavier 5 years ago
commit fc57a9db6c

4
.gitignore vendored

@ -16,3 +16,7 @@ captures
local.properties local.properties
node_modules node_modules
*xcuserdata* *xcuserdata*
*.sketch
/design
/releases
/screenshots

@ -41,8 +41,8 @@ public class AmPmCirclesView extends View {
private final Paint mPaint = new Paint(); private final Paint mPaint = new Paint();
private int mSelectedAlpha; private int mSelectedAlpha;
private int mUnselectedColor; private int mUnselectedColor;
private int mAmPmTextColor; protected int mAmPmTextColor = Color.WHITE;
private int mSelectedColor; protected int mSelectedColor = Color.BLUE;
private float mCircleRadiusMultiplier; private float mCircleRadiusMultiplier;
private float mAmPmCircleRadiusMultiplier; private float mAmPmCircleRadiusMultiplier;
private String mAmText; private String mAmText;
@ -73,8 +73,8 @@ public class AmPmCirclesView extends View {
Resources res = context.getResources(); Resources res = context.getResources();
mUnselectedColor = res.getColor(R.color.white); mUnselectedColor = res.getColor(R.color.white);
mSelectedColor = res.getColor(R.color.blue); //mSelectedColor = res.getColor(R.color.blue);
mAmPmTextColor = res.getColor(R.color.ampm_text_color); //mAmPmTextColor = res.getColor(R.color.ampm_text_color);
mSelectedAlpha = SELECTED_ALPHA; mSelectedAlpha = SELECTED_ALPHA;
String typefaceFamily = res.getString(R.string.sans_serif); String typefaceFamily = res.getString(R.string.sans_serif);
Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL); Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL);
@ -105,8 +105,8 @@ public class AmPmCirclesView extends View {
mSelectedAlpha = SELECTED_ALPHA_THEME_DARK; mSelectedAlpha = SELECTED_ALPHA_THEME_DARK;
} else { } else {
mUnselectedColor = res.getColor(R.color.white); mUnselectedColor = res.getColor(R.color.white);
mSelectedColor = res.getColor(R.color.blue); //mSelectedColor = res.getColor(R.color.blue);
mAmPmTextColor = res.getColor(R.color.ampm_text_color); //mAmPmTextColor = res.getColor(R.color.ampm_text_color);
mSelectedAlpha = SELECTED_ALPHA; mSelectedAlpha = SELECTED_ALPHA;
} }
} }

@ -84,6 +84,14 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private AnimatorSet mTransition; private AnimatorSet mTransition;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
public void setColor(int selectedColor)
{
mHourRadialSelectorView.mPaint.setColor(selectedColor);
mMinuteRadialSelectorView.mPaint.setColor(selectedColor);
mAmPmCirclesView.mSelectedColor = selectedColor;
mAmPmCirclesView.mAmPmTextColor = selectedColor;
}
public interface OnValueSelectedListener { public interface OnValueSelectedListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
} }

@ -40,7 +40,7 @@ public class RadialSelectorView extends View {
// Alpha level for the line. // Alpha level for the line.
private static final int FULL_ALPHA = Utils.FULL_ALPHA; private static final int FULL_ALPHA = Utils.FULL_ALPHA;
private final Paint mPaint = new Paint(); protected final Paint mPaint = new Paint();
private boolean mIsInitialized; private boolean mIsInitialized;
private boolean mDrawValuesReady; private boolean mDrawValuesReady;
@ -96,8 +96,6 @@ public class RadialSelectorView extends View {
Resources res = context.getResources(); Resources res = context.getResources();
int blue = res.getColor(R.color.blue);
mPaint.setColor(blue);
mPaint.setAntiAlias(true); mPaint.setAntiAlias(true);
mSelectionAlpha = SELECTED_ALPHA; mSelectionAlpha = SELECTED_ALPHA;
@ -139,15 +137,11 @@ public class RadialSelectorView extends View {
/* package */ void setTheme(Context context, boolean themeDark) { /* package */ void setTheme(Context context, boolean themeDark) {
Resources res = context.getResources(); Resources res = context.getResources();
int color;
if (themeDark) { if (themeDark) {
color = res.getColor(R.color.red);
mSelectionAlpha = SELECTED_ALPHA_THEME_DARK; mSelectionAlpha = SELECTED_ALPHA_THEME_DARK;
} else { } else {
color = res.getColor(R.color.blue);
mSelectionAlpha = SELECTED_ALPHA; mSelectionAlpha = SELECTED_ALPHA;
} }
mPaint.setColor(color);
} }
/** /**

@ -23,7 +23,9 @@ import android.app.*;
import android.content.*; import android.content.*;
import android.content.res.*; import android.content.res.*;
import android.os.*; import android.os.*;
import androidx.appcompat.app.*; import androidx.appcompat.app.*;
import android.util.*; import android.util.*;
import android.view.*; import android.view.*;
import android.view.View.*; import android.view.View.*;
@ -39,7 +41,8 @@ import java.util.*;
/** /**
* Dialog to set a time. * Dialog to set a time.
*/ */
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener{ public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener
{
private static final String TAG = "TimePickerDialog"; private static final String TAG = "TimePickerDialog";
private static final String KEY_HOUR_OF_DAY = "hour_of_day"; private static final String KEY_HOUR_OF_DAY = "hour_of_day";
@ -49,6 +52,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
private static final String KEY_IN_KB_MODE = "in_kb_mode"; private static final String KEY_IN_KB_MODE = "in_kb_mode";
private static final String KEY_TYPED_TIMES = "typed_times"; private static final String KEY_TYPED_TIMES = "typed_times";
private static final String KEY_DARK_THEME = "dark_theme"; private static final String KEY_DARK_THEME = "dark_theme";
private static final String KEY_SELECTED_COLOR = "selected_color";
public static final int HOUR_INDEX = 0; public static final int HOUR_INDEX = 0;
public static final int MINUTE_INDEX = 1; public static final int MINUTE_INDEX = 1;
@ -108,37 +112,50 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* The callback interface used to indicate the user is done filling in * The callback interface used to indicate the user is done filling in
* the time (they clicked on the 'Set' button). * the time (they clicked on the 'Set' button).
*/ */
public interface OnTimeSetListener { public interface OnTimeSetListener
{
/** /**
* @param view The view associated with this listener. * @param view The view associated with this listener.
* @param hourOfDay The hour that was set. * @param hourOfDay The hour that was set.
* @param minute The minute that was set. * @param minute The minute that was set.
*/ */
void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute); void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute);
default void onTimeCleared(RadialPickerLayout view) {} default void onTimeCleared(RadialPickerLayout view)
{
}
} }
public TimePickerDialog() { public TimePickerDialog()
{
// Empty constructor required for dialog fragment. // Empty constructor required for dialog fragment.
} }
@SuppressLint("Java") @SuppressLint("Java")
public TimePickerDialog(Context context, int theme, OnTimeSetListener callback, public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) { int hourOfDay, int minute, boolean is24HourMode)
{
// Empty constructor required for dialog fragment. // Empty constructor required for dialog fragment.
} }
public static TimePickerDialog newInstance(OnTimeSetListener callback, public static TimePickerDialog newInstance(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) { int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
TimePickerDialog ret = new TimePickerDialog(); TimePickerDialog ret = new TimePickerDialog();
ret.initialize(callback, hourOfDay, minute, is24HourMode); ret.initialize(callback, hourOfDay, minute, is24HourMode, color);
return ret; return ret;
} }
public void initialize(OnTimeSetListener callback, public void initialize(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) { int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
mCallback = callback; mCallback = callback;
mInitialHourOfDay = hourOfDay; mInitialHourOfDay = hourOfDay;
@ -146,40 +163,47 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mIs24HourMode = is24HourMode; mIs24HourMode = is24HourMode;
mInKbMode = false; mInKbMode = false;
mThemeDark = false; mThemeDark = false;
mSelectedColor = color;
} }
/** /**
* Set a dark or light theme. NOTE: this will only take effect for the next onCreateView. * Set a dark or light theme. NOTE: this will only take effect for the next onCreateView.
*/ */
public void setThemeDark(boolean dark) { public void setThemeDark(boolean dark)
{
mThemeDark = dark; mThemeDark = dark;
} }
public boolean isThemeDark() { public boolean isThemeDark()
{
return mThemeDark; return mThemeDark;
} }
public void setOnTimeSetListener(OnTimeSetListener callback) { public void setOnTimeSetListener(OnTimeSetListener callback)
{
mCallback = callback; mCallback = callback;
} }
public void setStartTime(int hourOfDay, int minute) { public void setStartTime(int hourOfDay, int minute)
{
mInitialHourOfDay = hourOfDay; mInitialHourOfDay = hourOfDay;
mInitialMinute = minute; mInitialMinute = minute;
mInKbMode = false; mInKbMode = false;
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY) if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY)
&& savedInstanceState.containsKey(KEY_MINUTE) && savedInstanceState.containsKey(KEY_MINUTE)
&& savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) { && savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY); mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY);
mInitialMinute = savedInstanceState.getInt(KEY_MINUTE); mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW); mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE); mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME); mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME);
mSelectedColor = savedInstanceState.getInt(KEY_SELECTED_COLOR);
} }
} }
@ -191,7 +215,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState)
{
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
View view = inflater.inflate(R.layout.time_picker_dialog, null); View view = inflater.inflate(R.layout.time_picker_dialog, null);
@ -203,8 +228,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mSelectHours = res.getString(R.string.select_hours); mSelectHours = res.getString(R.string.select_hours);
mMinutePickerDescription = res.getString(R.string.minute_picker_description); mMinutePickerDescription = res.getString(R.string.minute_picker_description);
mSelectMinutes = res.getString(R.string.select_minutes); mSelectMinutes = res.getString(R.string.select_minutes);
mSelectedColor = res.getColor(mThemeDark? R.color.red : R.color.blue); //mSelectedColor = res.getColor(mThemeDark ? R.color.red : R.color.blue);
mUnselectedColor = res.getColor(mThemeDark? R.color.white : R.color.numbers_text_color); mUnselectedColor = res.getColor(mThemeDark ? R.color.white : R.color.numbers_text_color);
mHourView = (TextView) view.findViewById(R.id.hours); mHourView = (TextView) view.findViewById(R.id.hours);
mHourView.setOnKeyListener(keyboardListener); mHourView.setOnKeyListener(keyboardListener);
@ -223,8 +248,9 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker); mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker);
mTimePicker.setOnValueSelectedListener(this); mTimePicker.setOnValueSelectedListener(this);
mTimePicker.setOnKeyListener(keyboardListener); mTimePicker.setOnKeyListener(keyboardListener);
mTimePicker.setColor(mSelectedColor);
mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay, mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay,
mInitialMinute, mIs24HourMode); mInitialMinute, mIs24HourMode);
int currentItemShowing = HOUR_INDEX; int currentItemShowing = HOUR_INDEX;
if (savedInstanceState != null && if (savedInstanceState != null &&
@ -234,25 +260,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
setCurrentItemShowing(currentItemShowing, false, true, true); setCurrentItemShowing(currentItemShowing, false, true, true);
mTimePicker.invalidate(); mTimePicker.invalidate();
mHourView.setOnClickListener(new OnClickListener() { mHourView.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
setCurrentItemShowing(HOUR_INDEX, true, false, true); setCurrentItemShowing(HOUR_INDEX, true, false, true);
tryVibrate(); tryVibrate();
} }
}); });
mMinuteView.setOnClickListener(new OnClickListener() { mMinuteView.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
setCurrentItemShowing(MINUTE_INDEX, true, false, true); setCurrentItemShowing(MINUTE_INDEX, true, false, true);
tryVibrate(); tryVibrate();
} }
}); });
mDoneButton = (TextView) view.findViewById(R.id.done_button); mDoneButton = (TextView) view.findViewById(R.id.done_button);
mDoneButton.setOnClickListener(new OnClickListener() { mDoneButton.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
if (mInKbMode && isTypedTimeFullyLegal()) { if (mInKbMode && isTypedTimeFullyLegal()) {
finishKbMode(false); finishKbMode(false);
} else { } else {
@ -260,7 +292,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
if (mCallback != null) { if (mCallback != null) {
mCallback.onTimeSet(mTimePicker, mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes()); mTimePicker.getHours(), mTimePicker.getMinutes());
} }
dismiss(); dismiss();
} }
@ -269,16 +301,16 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mClearButton = (TextView) view.findViewById(R.id.clear_button); mClearButton = (TextView) view.findViewById(R.id.clear_button);
mClearButton.setOnClickListener(new OnClickListener() mClearButton.setOnClickListener(new OnClickListener()
{ {
@Override @Override
public void onClick(View v) public void onClick(View v)
{ {
if(mCallback != null) { if (mCallback != null) {
mCallback.onTimeCleared(mTimePicker); mCallback.onTimeCleared(mTimePicker);
} }
dismiss(); dismiss();
} }
}); });
mClearButton.setOnKeyListener(keyboardListener); mClearButton.setOnKeyListener(keyboardListener);
// Enable or disable the AM/PM view. // Enable or disable the AM/PM view.
@ -293,15 +325,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
separatorView.setLayoutParams(paramsSeparator); separatorView.setLayoutParams(paramsSeparator);
} else { } else {
mAmPmTextView.setVisibility(View.VISIBLE); mAmPmTextView.setVisibility(View.VISIBLE);
updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM); updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM);
mAmPmHitspace.setOnClickListener(new OnClickListener() { mAmPmHitspace.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
tryVibrate(); tryVibrate();
int amOrPm = mTimePicker.getIsCurrentlyAmOrPm(); int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
if (amOrPm == AM) { if (amOrPm == AM) {
amOrPm = PM; amOrPm = PM;
} else if (amOrPm == PM){ } else if (amOrPm == PM) {
amOrPm = AM; amOrPm = AM;
} }
updateAmPmDisplay(amOrPm); updateAmPmDisplay(amOrPm);
@ -328,56 +362,61 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mTypedTimes = new ArrayList<Integer>(); mTypedTimes = new ArrayList<Integer>();
} }
// Set the theme at the end so that the initialize()s above don't counteract the theme.
mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark);
// Prepare some palette to use.
int white = res.getColor(R.color.white);
int circleBackground = res.getColor(R.color.circle_background);
int line = res.getColor(R.color.line_background);
int timeDisplay = res.getColor(R.color.numbers_text_color);
ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color);
int doneBackground = R.drawable.done_background_color;
int darkGray = res.getColor(R.color.dark_gray); // // Set the theme at the end so that the initialize()s above don't counteract the theme.
int lightGray = res.getColor(R.color.light_gray); // mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark);
int darkLine = res.getColor(R.color.line_dark); // // Prepare some palette to use.
ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark); // int white = res.getColor(R.color.white);
int darkDoneBackground = R.drawable.done_background_color_dark; // int circleBackground = res.getColor(R.color.circle_background);
// int line = res.getColor(R.color.line_background);
// int timeDisplay = res.getColor(R.color.numbers_text_color);
// ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color);
// int doneBackground = R.drawable.done_background_color;
//
// int darkGray = res.getColor(R.color.dark_gray);
// int lightGray = res.getColor(R.color.light_gray);
// int darkLine = res.getColor(R.color.line_dark);
// ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark);
// int darkDoneBackground = R.drawable.done_background_color_dark;
// Set the palette for each view based on the theme. // Set the palette for each view based on the theme.
view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white); // view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white);
view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white); // view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white);
((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay); // ((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay);
((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay); // ((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay);
view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line); // view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line);
mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor); // mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor);
mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground); // mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground); // mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground);
return view; return view;
} }
@Override @Override
public void onResume() { public void onResume()
{
super.onResume(); super.onResume();
mHapticFeedbackController.start(); mHapticFeedbackController.start();
} }
@Override @Override
public void onPause() { public void onPause()
{
super.onPause(); super.onPause();
mHapticFeedbackController.stop(); mHapticFeedbackController.stop();
} }
public void tryVibrate() { public void tryVibrate()
{
mHapticFeedbackController.tryVibrate(); mHapticFeedbackController.tryVibrate();
} }
private void updateAmPmDisplay(int amOrPm) { private void updateAmPmDisplay(int amOrPm)
{
if (amOrPm == AM) { if (amOrPm == AM) {
mAmPmTextView.setText(mAmText); mAmPmTextView.setText(mAmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mAmText); Utils.tryAccessibilityAnnounce(mTimePicker, mAmText);
mAmPmHitspace.setContentDescription(mAmText); mAmPmHitspace.setContentDescription(mAmText);
} else if (amOrPm == PM){ } else if (amOrPm == PM) {
mAmPmTextView.setText(mPmText); mAmPmTextView.setText(mPmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mPmText); Utils.tryAccessibilityAnnounce(mTimePicker, mPmText);
mAmPmHitspace.setContentDescription(mPmText); mAmPmHitspace.setContentDescription(mPmText);
@ -387,7 +426,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState)
{
if (mTimePicker != null) { if (mTimePicker != null) {
outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours()); outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours());
outState.putInt(KEY_MINUTE, mTimePicker.getMinutes()); outState.putInt(KEY_MINUTE, mTimePicker.getMinutes());
@ -398,6 +438,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes); outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
} }
outState.putBoolean(KEY_DARK_THEME, mThemeDark); outState.putBoolean(KEY_DARK_THEME, mThemeDark);
outState.putInt(KEY_SELECTED_COLOR, mSelectedColor);
} }
} }
@ -405,7 +446,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Called by the picker for updating the header display. * Called by the picker for updating the header display.
*/ */
@Override @Override
public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) { public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance)
{
if (pickerIndex == HOUR_INDEX) { if (pickerIndex == HOUR_INDEX) {
setHour(newValue, false); setHour(newValue, false);
String announcement = String.format("%d", newValue); String announcement = String.format("%d", newValue);
@ -417,7 +459,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
Utils.tryAccessibilityAnnounce(mTimePicker, announcement); Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
} else if (pickerIndex == MINUTE_INDEX){ } else if (pickerIndex == MINUTE_INDEX) {
setMinute(newValue); setMinute(newValue);
mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue); mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue);
} else if (pickerIndex == AMPM_INDEX) { } else if (pickerIndex == AMPM_INDEX) {
@ -430,7 +472,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private void setHour(int value, boolean announce) { private void setHour(int value, boolean announce)
{
String format; String format;
if (mIs24HourMode) { if (mIs24HourMode) {
format = "%02d"; format = "%02d";
@ -450,7 +493,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private void setMinute(int value) { private void setMinute(int value)
{
if (value == 60) { if (value == 60) {
value = 0; value = 0;
} }
@ -462,7 +506,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
// Show either Hours or Minutes. // Show either Hours or Minutes.
private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate, private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
boolean announce) { boolean announce)
{
mTimePicker.setCurrentItemShowing(index, animateCircle); mTimePicker.setCurrentItemShowing(index, animateCircle);
TextView labelToAnimate; TextView labelToAnimate;
@ -485,8 +530,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
labelToAnimate = mMinuteView; labelToAnimate = mMinuteView;
} }
int hourColor = (index == HOUR_INDEX)? mSelectedColor : mUnselectedColor; int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor;
int minuteColor = (index == MINUTE_INDEX)? mSelectedColor : mUnselectedColor; int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor;
mHourView.setTextColor(hourColor); mHourView.setTextColor(hourColor);
mMinuteView.setTextColor(minuteColor); mMinuteView.setTextColor(minuteColor);
@ -499,15 +544,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* For keyboard mode, processes key events. * For keyboard mode, processes key events.
*
* @param keyCode the pressed key. * @param keyCode the pressed key.
* @return true if the key was successfully processed, false otherwise. * @return true if the key was successfully processed, false otherwise.
*/ */
private boolean processKeyUp(int keyCode) { private boolean processKeyUp(int keyCode)
{
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
dismiss(); dismiss();
return true; return true;
} else if (keyCode == KeyEvent.KEYCODE_TAB) { } else if (keyCode == KeyEvent.KEYCODE_TAB) {
if(mInKbMode) { if (mInKbMode) {
if (isTypedTimeFullyLegal()) { if (isTypedTimeFullyLegal()) {
finishKbMode(true); finishKbMode(true);
} }
@ -522,7 +569,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
if (mCallback != null) { if (mCallback != null) {
mCallback.onTimeSet(mTimePicker, mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes()); mTimePicker.getHours(), mTimePicker.getMinutes());
} }
dismiss(); dismiss();
return true; return true;
@ -539,7 +586,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted)); deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
} }
Utils.tryAccessibilityAnnounce(mTimePicker, Utils.tryAccessibilityAnnounce(mTimePicker,
String.format(mDeletedKeyFormat, deletedKeyStr)); String.format(mDeletedKeyFormat, deletedKeyStr));
updateDisplay(true); updateDisplay(true);
} }
} }
@ -549,7 +596,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
|| keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7 || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
|| keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9 || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
|| (!mIs24HourMode && || (!mIs24HourMode &&
(keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) { (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
if (!mInKbMode) { if (!mInKbMode) {
if (mTimePicker == null) { if (mTimePicker == null) {
// Something's wrong, because time picker should definitely not be null. // Something's wrong, because time picker should definitely not be null.
@ -572,11 +619,13 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Try to start keyboard mode with the specified key, as long as the timepicker is not in the * Try to start keyboard mode with the specified key, as long as the timepicker is not in the
* middle of a touch-event. * middle of a touch-event.
*
* @param keyCode The key to use as the first press. Keyboard mode will not be started if the * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
* key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
* key. * key.
*/ */
private void tryStartingKbMode(int keyCode) { private void tryStartingKbMode(int keyCode)
{
if (mTimePicker.trySettingInputEnabled(false) && if (mTimePicker.trySettingInputEnabled(false) &&
(keyCode == -1 || addKeyIfLegal(keyCode))) { (keyCode == -1 || addKeyIfLegal(keyCode))) {
mInKbMode = true; mInKbMode = true;
@ -585,7 +634,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private boolean addKeyIfLegal(int keyCode) { private boolean addKeyIfLegal(int keyCode)
{
// If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode, // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
// we'll need to see if AM/PM have been typed. // we'll need to see if AM/PM have been typed.
if ((mIs24HourMode && mTypedTimes.size() == 4) || if ((mIs24HourMode && mTypedTimes.size() == 4) ||
@ -617,7 +667,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Traverse the tree to see if the keys that have been typed so far are legal as is, * Traverse the tree to see if the keys that have been typed so far are legal as is,
* or may become legal as more keys are typed (excluding backspace). * or may become legal as more keys are typed (excluding backspace).
*/ */
private boolean isTypedTimeLegalSoFar() { private boolean isTypedTimeLegalSoFar()
{
Node node = mLegalTimesTree; Node node = mLegalTimesTree;
for (int keyCode : mTypedTimes) { for (int keyCode : mTypedTimes) {
node = node.canReach(keyCode); node = node.canReach(keyCode);
@ -631,7 +682,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Check if the time that has been typed so far is completely legal, as is. * Check if the time that has been typed so far is completely legal, as is.
*/ */
private boolean isTypedTimeFullyLegal() { private boolean isTypedTimeFullyLegal()
{
if (mIs24HourMode) { if (mIs24HourMode) {
// For 24-hour mode, the time is legal if the hours and minutes are each legal. Note: // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
// getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode. // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
@ -645,7 +697,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private int deleteLastTypedKey() { private int deleteLastTypedKey()
{
int deleted = mTypedTimes.remove(mTypedTimes.size() - 1); int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
if (!isTypedTimeFullyLegal()) { if (!isTypedTimeFullyLegal()) {
mDoneButton.setEnabled(false); mDoneButton.setEnabled(false);
@ -655,9 +708,11 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
*
* @param changeDisplays If true, update the displays with the relevant time. * @param changeDisplays If true, update the displays with the relevant time.
*/ */
private void finishKbMode(boolean updateDisplays) { private void finishKbMode(boolean updateDisplays)
{
mInKbMode = false; mInKbMode = false;
if (!mTypedTimes.isEmpty()) { if (!mTypedTimes.isEmpty()) {
int values[] = getEnteredTime(null); int values[] = getEnteredTime(null);
@ -677,29 +732,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
* empty, either show an empty display (filled with the placeholder text), or update from the * empty, either show an empty display (filled with the placeholder text), or update from the
* timepicker's values. * timepicker's values.
*
* @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
* Otherwise, revert to the timepicker's values. * Otherwise, revert to the timepicker's values.
*/ */
private void updateDisplay(boolean allowEmptyDisplay) { private void updateDisplay(boolean allowEmptyDisplay)
{
if (!allowEmptyDisplay && mTypedTimes.isEmpty()) { if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
int hour = mTimePicker.getHours(); int hour = mTimePicker.getHours();
int minute = mTimePicker.getMinutes(); int minute = mTimePicker.getMinutes();
setHour(hour, true); setHour(hour, true);
setMinute(minute); setMinute(minute);
if (!mIs24HourMode) { if (!mIs24HourMode) {
updateAmPmDisplay(hour < 12? AM : PM); updateAmPmDisplay(hour < 12 ? AM : PM);
} }
setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true); setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true);
mDoneButton.setEnabled(true); mDoneButton.setEnabled(true);
} else { } else {
Boolean[] enteredZeros = {false, false}; Boolean[] enteredZeros = {false, false};
int[] values = getEnteredTime(enteredZeros); int[] values = getEnteredTime(enteredZeros);
String hourFormat = enteredZeros[0]? "%02d" : "%2d"; String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
String minuteFormat = (enteredZeros[1])? "%02d" : "%2d"; String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
String hourStr = (values[0] == -1)? mDoublePlaceholderText : String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
String.format(hourFormat, values[0]).replace(' ', mPlaceholderText); String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
String minuteStr = (values[1] == -1)? mDoublePlaceholderText : String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText); String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
mHourView.setText(hourStr); mHourView.setText(hourStr);
mHourSpaceView.setText(hourStr); mHourSpaceView.setText(hourStr);
mHourView.setTextColor(mUnselectedColor); mHourView.setTextColor(mUnselectedColor);
@ -712,7 +769,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private static int getValFromKeyCode(int keyCode) { private static int getValFromKeyCode(int keyCode)
{
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_0: case KeyEvent.KEYCODE_0:
return 0; return 0;
@ -741,20 +799,22 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Get the currently-entered time, as integer values of the hours and minutes typed. * Get the currently-entered time, as integer values of the hours and minutes typed.
*
* @param enteredZeros A size-2 boolean array, which the caller should initialize, and which * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
* may then be used for the caller to know whether zeros had been explicitly entered as either * may then be used for the caller to know whether zeros had been explicitly entered as either
* hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's. * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
* @return A size-3 int array. The first value will be the hours, the second value will be the * @return A size-3 int array. The first value will be the hours, the second value will be the
* minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM. * minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM.
*/ */
private int[] getEnteredTime(Boolean[] enteredZeros) { private int[] getEnteredTime(Boolean[] enteredZeros)
{
int amOrPm = -1; int amOrPm = -1;
int startIndex = 1; int startIndex = 1;
if (!mIs24HourMode && isTypedTimeFullyLegal()) { if (!mIs24HourMode && isTypedTimeFullyLegal()) {
int keyCode = mTypedTimes.get(mTypedTimes.size() - 1); int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
if (keyCode == getAmOrPmKeyCode(AM)) { if (keyCode == getAmOrPmKeyCode(AM)) {
amOrPm = AM; amOrPm = AM;
} else if (keyCode == getAmOrPmKeyCode(PM)){ } else if (keyCode == getAmOrPmKeyCode(PM)) {
amOrPm = PM; amOrPm = PM;
} }
startIndex = 2; startIndex = 2;
@ -765,15 +825,15 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i)); int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
if (i == startIndex) { if (i == startIndex) {
minute = val; minute = val;
} else if (i == startIndex+1) { } else if (i == startIndex + 1) {
minute += 10*val; minute += 10 * val;
if (enteredZeros != null && val == 0) { if (enteredZeros != null && val == 0) {
enteredZeros[1] = true; enteredZeros[1] = true;
} }
} else if (i == startIndex+2) { } else if (i == startIndex + 2) {
hour = val; hour = val;
} else if (i == startIndex+3) { } else if (i == startIndex + 3) {
hour += 10*val; hour += 10 * val;
if (enteredZeros != null && val == 0) { if (enteredZeros != null && val == 0) {
enteredZeros[0] = true; enteredZeros[0] = true;
} }
@ -787,7 +847,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Get the keycode value for AM and PM in the current language. * Get the keycode value for AM and PM in the current language.
*/ */
private int getAmOrPmKeyCode(int amOrPm) { private int getAmOrPmKeyCode(int amOrPm)
{
// Cache the codes. // Cache the codes.
if (mAmKeyCode == -1 || mPmKeyCode == -1) { if (mAmKeyCode == -1 || mPmKeyCode == -1) {
// Find the first character in the AM/PM text that is unique. // Find the first character in the AM/PM text that is unique.
@ -822,7 +883,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/** /**
* Create a tree for deciding what keys can legally be typed. * Create a tree for deciding what keys can legally be typed.
*/ */
private void generateLegalTimesTree() { private void generateLegalTimesTree()
{
// Create a quick cache of numbers to their keycodes. // Create a quick cache of numbers to their keycodes.
int k0 = KeyEvent.KEYCODE_0; int k0 = KeyEvent.KEYCODE_0;
int k1 = KeyEvent.KEYCODE_1; int k1 = KeyEvent.KEYCODE_1;
@ -955,20 +1017,24 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* mLegalKeys represents the keys that can be typed to get to the node. * mLegalKeys represents the keys that can be typed to get to the node.
* mChildren are the children that can be reached from this node. * mChildren are the children that can be reached from this node.
*/ */
private class Node { private class Node
{
private int[] mLegalKeys; private int[] mLegalKeys;
private ArrayList<Node> mChildren; private ArrayList<Node> mChildren;
public Node(int... legalKeys) { public Node(int... legalKeys)
{
mLegalKeys = legalKeys; mLegalKeys = legalKeys;
mChildren = new ArrayList<Node>(); mChildren = new ArrayList<Node>();
} }
public void addChild(Node child) { public void addChild(Node child)
{
mChildren.add(child); mChildren.add(child);
} }
public boolean containsKey(int key) { public boolean containsKey(int key)
{
for (int i = 0; i < mLegalKeys.length; i++) { for (int i = 0; i < mLegalKeys.length; i++) {
if (mLegalKeys[i] == key) { if (mLegalKeys[i] == key) {
return true; return true;
@ -977,7 +1043,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
return false; return false;
} }
public Node canReach(int key) { public Node canReach(int key)
{
if (mChildren == null) { if (mChildren == null) {
return null; return null;
} }
@ -990,9 +1057,11 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
private class KeyboardListener implements OnKeyListener { private class KeyboardListener implements OnKeyListener
{
@Override @Override
public boolean onKey(View v, int keyCode, KeyEvent event) { public boolean onKey(View v, int keyCode, KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_UP) { if (event.getAction() == KeyEvent.ACTION_UP) {
return processKeyUp(keyCode); return processKeyUp(keyCode);
} }
@ -1000,14 +1069,16 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
} }
} }
public void setDismissListener( DialogInterface.OnDismissListener listener ) { public void setDismissListener(DialogInterface.OnDismissListener listener)
{
dismissListener = listener; dismissListener = listener;
} }
@Override @Override
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog); super.onDismiss(dialog);
if( dismissListener != null ) if (dismissListener != null)
dismissListener.onDismiss(dialog); dismissListener.onDismiss(dialog);
} }
} }

@ -49,33 +49,33 @@
android:layout_height="1dip" android:layout_height="1dip"
android:background="@color/line_background" /> android:background="@color/line_background" />
<LinearLayout <androidx.appcompat.widget.LinearLayoutCompat
style="?android:attr/buttonBarStyle" style="?android:attr/buttonBarStyle"
android:layout_width="@dimen/date_picker_component_width" android:layout_width="@dimen/date_picker_component_width"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" > android:orientation="horizontal" >
<Button <androidx.appcompat.widget.AppCompatButton
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/clear_button" android:id="@+id/clear_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/done_background_color"
android:minHeight="48dp" android:minHeight="48dp"
android:textColor="#333"
android:text="@string/clear_label" android:text="@string/clear_label"
android:textColor="@color/done_text_color"
android:textSize="@dimen/done_label_size" /> android:textSize="@dimen/done_label_size" />
<Button <androidx.appcompat.widget.AppCompatButton
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/done_button" android:id="@+id/done_button"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/done_background_color"
android:minHeight="48dp" android:minHeight="48dp"
android:textColor="#333"
android:text="@string/done_label" android:text="@string/done_label"
android:textColor="@color/done_text_color"
android:textSize="@dimen/done_label_size" /> android:textSize="@dimen/done_label_size" />
</LinearLayout> </androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout> </LinearLayout>

@ -76,7 +76,7 @@ build_apk() {
fi fi
log_info "Building debug APK" log_info "Building debug APK"
./gradlew assembleDebug || fail ./gradlew assembleDebug --stacktrace || fail
cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk
} }

@ -4,6 +4,7 @@ plugins {
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-kapt' id 'kotlin-kapt'
id 'com.github.triplet.play' version '2.6.2' id 'com.github.triplet.play' version '2.6.2'
id 'kotlin-android-extensions'
} }
android { android {
@ -69,6 +70,10 @@ android {
sourceSets { sourceSets {
main.assets.srcDirs += '../uhabits-core/src/main/resources/' main.assets.srcDirs += '../uhabits-core/src/main/resources/'
} }
buildFeatures {
viewBinding true
}
} }
dependencies { dependencies {
@ -89,6 +94,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
compileOnly "javax.annotation:jsr250-api:1.0" compileOnly "javax.annotation:jsr250-api:1.0"
compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION" compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"

@ -54,12 +54,15 @@ public class HabitsTest extends BaseUserInterfaceTest
verifyShowsScreen(LIST_HABITS); verifyShowsScreen(LIST_HABITS);
clickMenu(ADD); clickMenu(ADD);
verifyShowsScreen(SELECT_HABIT_TYPE);
clickText("Yes or No");
verifyShowsScreen(EDIT_HABIT); verifyShowsScreen(EDIT_HABIT);
String testName = "Hello world"; String testName = "Hello world";
typeName(testName); typeName(testName);
typeQuestion("Did you say hello to the world today?"); typeQuestion("Did you say hello to the world today?");
typeDescription(description); typeDescription(description);
pickFrequency("Every week"); pickFrequency();
pickColor(5); pickColor(5);
clickSave(); clickSave();

@ -152,7 +152,7 @@ public class CommonSteps extends BaseUserInterfaceTest
public enum Screen public enum Screen
{ {
LIST_HABITS, SHOW_HABIT, EDIT_HABIT LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE
} }
public static void verifyShowsScreen(Screen screen) { public static void verifyShowsScreen(Screen screen) {
@ -176,9 +176,15 @@ public class CommonSteps extends BaseUserInterfaceTest
break; break;
case EDIT_HABIT: case EDIT_HABIT:
onView(withId(R.id.tvQuestion)).check(matches(isDisplayed())); onView(withId(R.id.questionInput)).check(matches(isDisplayed()));
onView(withId(R.id.tvDescription)).check(matches(isDisplayed()));
break; break;
case SELECT_HABIT_TYPE:
onView(withText(R.string.yes_or_no_example)).check(matches(isDisplayed()));
break;
default:
throw new IllegalStateException();
} }
} }
} }

@ -36,42 +36,42 @@ public class EditHabitSteps
onView(withId(R.id.buttonSave)).perform(click()); onView(withId(R.id.buttonSave)).perform(click());
} }
public static void pickFrequency(String freq) public static void pickFrequency()
{ {
onView(withId(R.id.spinner)).perform(click()); onView(withId(R.id.frequencyPicker)).perform(click());
device.findObject(By.text(freq)).click(); onView(withText("SAVE")).perform(click());
} }
public static void pickColor(int color) public static void pickColor(int color)
{ {
onView(withId(R.id.buttonPickColor)).perform(click()); onView(withId(R.id.colorButton)).perform(click());
device.findObject(By.descStartsWith(String.format("Color %d", color))).click(); device.findObject(By.descStartsWith(String.format("Color %d", color))).click();
} }
public static void typeName(String name) public static void typeName(String name)
{ {
typeTextWithId(R.id.tvName, name); typeTextWithId(R.id.nameInput, name);
} }
public static void typeQuestion(String name) public static void typeQuestion(String name)
{ {
typeTextWithId(R.id.tvQuestion, name); typeTextWithId(R.id.questionInput, name);
} }
public static void typeDescription(String description) public static void typeDescription(String description)
{ {
typeTextWithId(R.id.tvDescription, description); typeTextWithId(R.id.notesInput, description);
} }
public static void setReminder() public static void setReminder()
{ {
onView(withId(R.id.tvReminderTime)).perform(click()); onView(withId(R.id.reminderTimePicker)).perform(click());
onView(withId(R.id.done_button)).perform(click()); onView(withId(R.id.done_button)).perform(click());
} }
public static void clickReminderDays() public static void clickReminderDays()
{ {
onView(withId(R.id.tvReminderDays)).perform(click()); onView(withId(R.id.reminderDatePicker)).perform(click());
} }
public static void unselectAllDays() public static void unselectAllDays()

@ -60,7 +60,7 @@ public abstract class ListHabitsSteps
break; break;
case ADD: case ADD:
clickViewWithId(R.id.actionCreateBooleanHabit); clickViewWithId(R.id.actionCreateHabit);
break; break;
case EDIT: case EDIT:

@ -1,229 +1,215 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> package="org.isoron.uhabits">
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application <application
android:name=".HabitsApplication" android:name=".HabitsApplication"
android:allowBackup="true" android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent" android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title" android:label="@string/main_activity_title"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppBaseTheme"> android:theme="@style/AppBaseTheme">
<activity
android:exported="true"
android:name=".activities.habits.edit.EditHabitActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/> android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
<activity <activity
android:name=".activities.habits.list.ListHabitsActivity" android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true" android:exported="true"
android:label="@string/main_activity_title" android:label="@string/main_activity_title"
android:launchMode="singleTop"/> android:launchMode="singleTop" />
<activity-alias <activity-alias
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/main_activity_title" android:label="@string/main_activity_title"
android:launchMode="singleTop" android:launchMode="singleTop"
android:targetActivity=".activities.habits.list.ListHabitsActivity"> android:targetActivity=".activities.habits.list.ListHabitsActivity">
<intent-filter android:label="@string/main_activity_title"> <intent-filter android:label="@string/main_activity_title">
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
<activity <activity
android:name=".activities.habits.show.ShowHabitActivity" android:name=".activities.habits.show.ShowHabitActivity"
android:label="@string/title_activity_show_habit"> android:label="@string/title_activity_show_habit">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/> android:value=".activities.habits.list.ListHabitsActivity" />
</activity> </activity>
<activity <activity
android:name=".activities.settings.SettingsActivity" android:name=".activities.settings.SettingsActivity"
android:label="@string/settings"> android:label="@string/settings">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/> android:value=".activities.habits.list.ListHabitsActivity" />
</activity> </activity>
<activity <activity
android:name=".activities.intro.IntroActivity" android:name=".activities.intro.IntroActivity"
android:label="" android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity <activity
android:name=".widgets.HabitPickerDialog" android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog"> android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activities.about.AboutActivity" android:name=".activities.about.AboutActivity"
android:label="@string/about"> android:label="@string/about">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/> android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity android:name=".notifications.SnoozeDelayPickerActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity> </activity>
<activity
android:name=".notifications.SnoozeDelayPickerActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar"></activity>
<receiver <receiver
android:name=".widgets.CheckmarkWidgetProvider" android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark"> android:label="@string/checkmark">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/> android:resource="@xml/widget_checkmark_info" />
</receiver> </receiver>
<service android:name=".widgets.StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" <service
android:exported="false" /> android:name=".widgets.StackWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver <receiver
android:name=".widgets.HistoryWidgetProvider" android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history"> android:label="@string/history">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/> android:resource="@xml/widget_history_info" />
</receiver> </receiver>
<receiver <receiver
android:name=".widgets.ScoreWidgetProvider" android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength"> android:label="@string/habit_strength">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/> android:resource="@xml/widget_score_info" />
</receiver> </receiver>
<receiver <receiver
android:name=".widgets.StreakWidgetProvider" android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks"> android:label="@string/streaks">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/> android:resource="@xml/widget_streak_info" />
</receiver> </receiver>
<receiver <receiver
android:name=".widgets.FrequencyWidgetProvider" android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency"> android:label="@string/frequency">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/> android:resource="@xml/widget_frequency_info" />
</receiver> </receiver>
<receiver android:name=".receivers.ReminderReceiver"> <receiver android:name=".receivers.ReminderReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receivers.WidgetReceiver"> <receiver android:name=".receivers.WidgetReceiver">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_TOGGLE_REPETITION"/> <action android:name="org.isoron.uhabits.ACTION_TOGGLE_REPETITION" />
<data <data
android:host="org.isoron.uhabits" android:host="org.isoron.uhabits"
android:scheme="content"/> android:scheme="content" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_ADD_REPETITION"/> <action android:name="org.isoron.uhabits.ACTION_ADD_REPETITION" />
<data <data
android:host="org.isoron.uhabits" android:host="org.isoron.uhabits"
android:scheme="content"/> android:scheme="content" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<action android:name="org.isoron.uhabits.ACTION_REMOVE_REPETITION"/> <action android:name="org.isoron.uhabits.ACTION_REMOVE_REPETITION" />
<data <data
android:host="org.isoron.uhabits" android:host="org.isoron.uhabits"
android:scheme="content"/> android:scheme="content" />
</intent-filter> </intent-filter>
</receiver> </receiver> <!-- Locale/Tasker -->
<!-- Locale/Tasker -->
<activity <activity
android:name=".automation.EditSettingActivity" android:name=".automation.EditSettingActivity"
android:exported="true" android:exported="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING"/> <action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter> </intent-filter>
</activity> </activity> <!-- Locale/Tasker -->
<!-- Locale/Tasker -->
<receiver <receiver
android:name=".automation.FireSettingReceiver" android:name=".automation.FireSettingReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/> <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.isoron.uhabits" android:authorities="org.isoron.uhabits"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/> android:resource="@xml/file_paths" />
</provider> </provider>
<service <service
android:name=".sync.SyncService" android:name=".sync.SyncService"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false"></service>
</service>
</application> </application>
</manifest> </manifest>

@ -19,6 +19,7 @@
package org.isoron.uhabits.activities package org.isoron.uhabits.activities
import android.app.*
import android.content.res.Configuration.* import android.content.res.Configuration.*
import android.os.Build.VERSION.* import android.os.Build.VERSION.*
import androidx.core.content.* import androidx.core.content.*
@ -30,8 +31,8 @@ import javax.inject.*
@ActivityScope @ActivityScope
class AndroidThemeSwitcher class AndroidThemeSwitcher
@Inject constructor( constructor(
private val activity: BaseActivity, private val activity: Activity,
preferences: Preferences preferences: Preferences
) : ThemeSwitcher(preferences) { ) : ThemeSwitcher(preferences) {

@ -48,5 +48,5 @@ interface HabitsActivityComponent {
val listHabitsScreen: ListHabitsScreen val listHabitsScreen: ListHabitsScreen
val listHabitsSelectionMenu: ListHabitsSelectionMenu val listHabitsSelectionMenu: ListHabitsSelectionMenu
val showHabitScreen: ShowHabitScreen val showHabitScreen: ShowHabitScreen
val themeSwitcher: AndroidThemeSwitcher val themeSwitcher: ThemeSwitcher
} }

@ -21,10 +21,17 @@ package org.isoron.uhabits.activities
import dagger.* import dagger.*
import org.isoron.androidbase.activities.* import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.* import org.isoron.uhabits.core.ui.*
@Module @Module
abstract class HabitsActivityModule { class HabitsActivityModule {
@Binds @ActivityScope
internal abstract fun getThemeSwitcher(t: AndroidThemeSwitcher): ThemeSwitcher @Provides
@ActivityScope
fun getThemeSwitcher(activity: BaseActivity,
prefs: Preferences
): ThemeSwitcher {
return AndroidThemeSwitcher(activity, prefs)
}
} }

@ -0,0 +1,172 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.common.dialogs
import android.app.*
import android.os.*
import android.util.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.frequency_picker_dialog.view.*
import org.isoron.uhabits.*
class FrequencyPickerDialog(var freqNumerator: Int,
var freqDenominator: Int
) : AppCompatDialogFragment() {
lateinit var contentView: View
var onFrequencyPicked: (num: Int, den: Int) -> Unit = {_,_ -> }
constructor() : this(1, 1)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = LayoutInflater.from(activity!!)
contentView = inflater.inflate(R.layout.frequency_picker_dialog, null)
contentView.everyDayRadioButton.setOnClickListener {
check(contentView.everyDayRadioButton)
unfocusAll()
}
contentView.everyXDaysRadioButton.setOnClickListener {
check(contentView.everyXDaysRadioButton)
val everyXDaysTextView = contentView.everyXDaysTextView
focus(everyXDaysTextView)
}
contentView.everyXDaysTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.everyXDaysRadioButton)
}
contentView.xTimesPerWeekRadioButton.setOnClickListener {
check(contentView.xTimesPerWeekRadioButton)
focus(contentView.xTimesPerWeekTextView)
}
contentView.xTimesPerWeekTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerWeekRadioButton)
}
contentView.xTimesPerMonthRadioButton.setOnClickListener {
check(contentView.xTimesPerMonthRadioButton)
focus(contentView.xTimesPerMonthTextView)
}
contentView.xTimesPerMonthTextView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) check(contentView.xTimesPerMonthRadioButton)
}
return AlertDialog.Builder(activity!!)
.setView(contentView)
.setPositiveButton(R.string.save) { _, _ -> onSaveClicked() }
.create()
}
private fun onSaveClicked() {
var numerator = 1
var denominator = 1
when {
contentView.everyDayRadioButton.isChecked -> {
// NOP
}
contentView.everyXDaysRadioButton.isChecked -> {
if (contentView.everyXDaysTextView.text.isNotEmpty()) {
denominator = Integer.parseInt(contentView.everyXDaysTextView.text.toString())
}
}
contentView.xTimesPerWeekRadioButton.isChecked -> {
if (contentView.xTimesPerWeekTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerWeekTextView.text.toString())
denominator = 7
}
}
else -> {
if (contentView.xTimesPerMonthTextView.text.isNotEmpty()) {
numerator = Integer.parseInt(contentView.xTimesPerMonthTextView.text.toString())
denominator = 30
}
}
}
if (numerator >= denominator || numerator < 1) {
numerator = 1
denominator = 1
}
onFrequencyPicked(numerator, denominator)
dismiss()
}
private fun check(view: RadioButton?) {
uncheckAll()
view?.isChecked = true
view?.requestFocus()
}
override fun onResume() {
super.onResume()
populateViews()
}
private fun populateViews() {
uncheckAll()
if (freqNumerator == 1) {
if(freqDenominator == 1) {
contentView.everyDayRadioButton.isChecked = true
} else {
contentView.everyXDaysRadioButton.isChecked = true
contentView.everyXDaysTextView.setText(freqDenominator.toString())
focus(contentView.everyXDaysTextView)
}
} else {
if(freqDenominator == 7) {
contentView.xTimesPerWeekRadioButton.isChecked = true
contentView.xTimesPerWeekTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerWeekTextView)
} else if (freqDenominator == 30 || freqDenominator == 31) {
contentView.xTimesPerMonthRadioButton.isChecked = true
contentView.xTimesPerMonthTextView.setText(freqNumerator.toString())
focus(contentView.xTimesPerMonthTextView)
} else {
Log.w("FrequencyPickerDialog", "Unknown frequency: $freqNumerator/$freqDenominator")
contentView.everyDayRadioButton.isChecked = true
}
}
}
private fun focus(view: EditText) {
view.requestFocus()
view.setSelection(view.text.length)
}
private fun uncheckAll() {
contentView.everyDayRadioButton.isChecked = false
contentView.everyXDaysRadioButton.isChecked = false
contentView.xTimesPerWeekRadioButton.isChecked = false
contentView.xTimesPerMonthRadioButton.isChecked = false
}
private fun unfocusAll() {
contentView.everyXDaysTextView.clearFocus()
contentView.xTimesPerWeekTextView.clearFocus()
contentView.xTimesPerMonthTextView.clearFocus()
}
}

@ -0,0 +1,250 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit
import android.content.res.*
import android.graphics.*
import android.os.*
import android.text.format.*
import android.view.*
import androidx.appcompat.app.*
import androidx.fragment.app.*
import com.android.datetimepicker.time.*
import kotlinx.android.synthetic.main.activity_edit_habit.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.*
import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.*
import org.isoron.uhabits.core.commands.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.utils.*
class EditHabitActivity : AppCompatActivity() {
private lateinit var themeSwitcher: AndroidThemeSwitcher
private lateinit var binding: ActivityEditHabitBinding
private lateinit var commandRunner: CommandRunner
var habitId = -1L
var paletteColor = 11
var androidColor = 0
var freqNum = 1
var freqDen = 1
var reminderHour = -1
var reminderMin = -1
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
override fun onCreate(state: Bundle?) {
super.onCreate(state)
val component = (application as HabitsApplication).component
themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
themeSwitcher.apply()
binding = ActivityEditHabitBinding.inflate(layoutInflater)
setContentView(binding.root)
if (intent.hasExtra("habitId")) {
binding.toolbar.title = getString(R.string.edit_habit)
habitId = intent.getLongExtra("habitId", -1)
val habit = component.habitList.getById(habitId)!!
paletteColor = habit.color
freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator
if (habit.hasReminder()) {
reminderHour = habit.reminder.hour
reminderMin = habit.reminder.minute
reminderDays = habit.reminder.days
}
binding.nameInput.setText(habit.name)
binding.questionInput.setText(habit.question)
binding.notesInput.setText(habit.description)
}
if (state != null) {
habitId = state.getLong("habitId")
paletteColor = state.getInt("paletteColor")
freqNum = state.getInt("freqNum")
freqDen = state.getInt("freqDen")
reminderHour = state.getInt("reminderHour")
reminderMin = state.getInt("reminderMin")
reminderDays = WeekdayList(state.getInt("reminderDays"))
}
updateColors()
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.elevation = 10.0f
val colorPickerDialogFactory = ColorPickerDialogFactory(this)
binding.colorButton.setOnClickListener {
val dialog = colorPickerDialogFactory.create(paletteColor)
dialog.setListener { paletteColor ->
this.paletteColor = paletteColor
updateColors()
}
dialog.show(supportFragmentManager, "colorPicker")
}
populateFrequency()
binding.frequencyPicker.setOnClickListener {
val dialog = FrequencyPickerDialog(freqNum, freqDen)
dialog.onFrequencyPicked = { num, den ->
freqNum = num
freqDen = den
populateFrequency()
}
dialog.show(supportFragmentManager, "frequencyPicker")
}
populateReminder()
binding.reminderTimePicker.setOnClickListener {
val currentHour = if (reminderHour >= 0) reminderHour else 8
val currentMin = if (reminderMin >= 0) reminderMin else 0
val is24HourMode = DateFormat.is24HourFormat(this)
val dialog = TimePickerDialog.newInstance(object : TimePickerDialog.OnTimeSetListener {
override fun onTimeSet(view: RadialPickerLayout?, hourOfDay: Int, minute: Int) {
reminderHour = hourOfDay
reminderMin = minute
populateReminder()
}
override fun onTimeCleared(view: RadialPickerLayout?) {
reminderHour = -1
reminderMin = -1
reminderDays = WeekdayList.EVERY_DAY
populateReminder()
}
}, currentHour, currentMin, is24HourMode, androidColor)
dialog.show(supportFragmentManager, "timePicker")
}
binding.reminderDatePicker.setOnClickListener {
val dialog = WeekdayPickerDialog()
dialog.setListener { days ->
reminderDays = days
if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY
populateReminder()
}
dialog.setSelectedDays(reminderDays)
dialog.show(supportFragmentManager, "dayPicker")
}
binding.buttonSave.setOnClickListener {
if(validate()) save()
}
for (fragment in supportFragmentManager.fragments) {
(fragment as DialogFragment).dismiss()
}
}
private fun save() {
val component = (application as HabitsApplication).component
val habit = component.modelFactory.buildHabit()
var original: Habit? = null
if (habitId >= 0) {
original = component.habitList.getById(habitId)!!
habit.copyFrom(original)
}
habit.name = nameInput.text.trim().toString()
habit.question = questionInput.text.trim().toString()
habit.description = notesInput.text.trim().toString()
habit.color = paletteColor
if (reminderHour >= 0) {
habit.setReminder(Reminder(reminderHour, reminderMin, reminderDays))
}
habit.frequency = Frequency(freqNum, freqDen)
habit.unit = ""
habit.targetValue = 1.0
habit.type = Habit.YES_NO_HABIT
val command = if (habitId >= 0) {
component.editHabitCommandFactory.create(component.habitList, original, habit)
} else {
component.createHabitCommandFactory.create(component.habitList, habit)
}
component.commandRunner.execute(command, null)
finish()
}
private fun validate(): Boolean {
if (nameInput.text.isEmpty()) {
nameInput.error = getString(R.string.validation_name_should_not_be_blank)
return false
}
return true
}
private fun populateReminder() {
if (reminderHour < 0) {
binding.reminderTimePicker.text = getString(R.string.reminder_off)
binding.reminderDatePicker.visibility = View.GONE
binding.reminderDivider.visibility = View.GONE
} else {
val time = AndroidDateUtils.formatTime(this, reminderHour, reminderMin)
val daysArray = reminderDays.toArray()
binding.reminderTimePicker.text = time
binding.reminderDatePicker.visibility = View.VISIBLE
binding.reminderDivider.visibility = View.VISIBLE
binding.reminderDatePicker.text = AndroidDateUtils.formatWeekdayList(this, daysArray)
}
}
private fun populateFrequency() {
val label = when {
freqNum == 1 && freqDen == 1 -> getString(R.string.every_day)
freqNum == 1 && freqDen == 7 -> getString(R.string.every_week)
freqNum == 1 && freqDen > 1 -> getString(R.string.every_x_days, freqDen)
freqDen == 7 -> getString(R.string.x_times_per_week, freqNum)
freqDen == 31 -> getString(R.string.x_times_per_month, freqNum)
else -> "Unknown"
}
binding.frequencyPicker.text = label
}
private fun updateColors() {
androidColor = PaletteUtils.getColor(this, paletteColor)
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
if (!themeSwitcher.isNightMode) {
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
window.statusBarColor = darkerAndroidColor
binding.toolbar.setBackgroundColor(androidColor)
}
}
override fun onSaveInstanceState(state: Bundle) {
super.onSaveInstanceState(state)
with(state) {
putLong("habitId", habitId)
putInt("paletteColor", paletteColor)
putInt("androidColor", androidColor)
putInt("freqNum", freqNum)
putInt("freqDen", freqDen)
putInt("reminderHour", reminderHour)
putInt("reminderMin", reminderMin)
putInt("reminderDays", reminderDays.toInteger())
}
}
}

@ -1,290 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit;
import android.app.*;
import android.content.*;
import android.os.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.*;
import android.text.format.*;
import android.view.*;
import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.uhabits.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.*;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.activities.habits.edit.views.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import butterknife.*;
import static android.view.View.*;
public class EditHabitDialog extends AppCompatDialogFragment
{
public static final String BUNDLE_HABIT_ID = "habitId";
public static final String BUNDLE_HABIT_TYPE = "habitType";
private static final String WEEKDAY_PICKER_TAG = "weekdayPicker";
protected Habit originalHabit;
protected Preferences prefs;
protected CommandRunner commandRunner;
protected HabitList habitList;
protected HabitsApplicationComponent component;
protected ModelFactory modelFactory;
@BindView(R.id.namePanel)
NameDescriptionPanel namePanel;
@BindView(R.id.reminderPanel)
ReminderPanel reminderPanel;
@BindView(R.id.frequencyPanel)
FrequencyPanel frequencyPanel;
@BindView(R.id.targetPanel)
TargetPanel targetPanel;
private ColorPickerDialogFactory colorPickerDialogFactory;
@Override
public int getTheme()
{
HabitsActivity activity = (HabitsActivity) getActivity();
return activity.getComponent().getThemeSwitcher().getDialogTheme();
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
HabitsActivity activity = (HabitsActivity) getActivity();
colorPickerDialogFactory =
activity.getComponent().getColorPickerDialogFactory();
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view;
view = inflater.inflate(R.layout.edit_habit, container, false);
initDependencies();
ButterKnife.bind(this, view);
originalHabit = parseHabitFromArguments();
getDialog().setTitle(getTitle());
populateForm();
setupReminderController();
setupNameController();
restoreChildFragmentListeners();
return view;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
final Window window = dialog.getWindow();
if (window != null) {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
return dialog;
}
protected int getTitle()
{
if (originalHabit != null) return R.string.edit_habit;
else return R.string.create_habit;
}
protected void saveHabit(@NonNull Habit habit)
{
if (originalHabit == null)
{
commandRunner.execute(component
.getCreateHabitCommandFactory()
.create(habitList, habit), null);
}
else
{
commandRunner.execute(component.getEditHabitCommandFactory().
create(habitList, originalHabit, habit), originalHabit.getId());
}
}
private int getTypeFromArguments()
{
return getArguments().getInt(BUNDLE_HABIT_TYPE);
}
private void initDependencies()
{
Context appContext = getContext().getApplicationContext();
HabitsApplication app = (HabitsApplication) appContext;
component = app.getComponent();
prefs = component.getPreferences();
habitList = component.getHabitList();
commandRunner = component.getCommandRunner();
modelFactory = component.getModelFactory();
}
@OnClick(R.id.buttonDiscard)
void onButtonDiscardClick()
{
dismiss();
}
@OnClick(R.id.buttonSave)
void onSaveButtonClick()
{
int type = getTypeFromArguments();
if (!namePanel.validate()) return;
if (type == Habit.YES_NO_HABIT && !frequencyPanel.validate()) return;
if (type == Habit.NUMBER_HABIT && !targetPanel.validate()) return;
Habit habit = modelFactory.buildHabit();
if( originalHabit != null )
habit.copyFrom(originalHabit);
habit.setName(namePanel.getName());
habit.setDescription(namePanel.getDescription());
habit.setQuestion(namePanel.getQuestion());
habit.setColor(namePanel.getColor());
habit.setReminder(reminderPanel.getReminder());
habit.setFrequency(frequencyPanel.getFrequency());
habit.setUnit(targetPanel.getUnit());
habit.setTargetValue(targetPanel.getTargetValue());
habit.setType(type);
saveHabit(habit);
dismiss();
}
@Nullable
private Habit parseHabitFromArguments()
{
Bundle arguments = getArguments();
if (arguments == null) return null;
Long id = (Long) arguments.get(BUNDLE_HABIT_ID);
if (id == null) return null;
Habit habit = habitList.getById(id);
if (habit == null) throw new IllegalStateException();
return habit;
}
private void populateForm()
{
Habit habit = modelFactory.buildHabit();
habit.setFrequency(Frequency.DAILY);
habit.setColor(prefs.getDefaultHabitColor(habit.getColor()));
habit.setType(getTypeFromArguments());
if (originalHabit != null) habit.copyFrom(originalHabit);
if (habit.isNumerical()) frequencyPanel.setVisibility(GONE);
else targetPanel.setVisibility(GONE);
namePanel.populateFrom(habit);
frequencyPanel.setFrequency(habit.getFrequency());
targetPanel.setTargetValue(habit.getTargetValue());
targetPanel.setUnit(habit.getUnit());
if (habit.hasReminder()) reminderPanel.setReminder(habit.getReminder());
}
private void setupNameController()
{
namePanel.setController(new NameDescriptionPanel.Controller()
{
@Override
public void onColorPickerClicked(int previousColor)
{
ColorPickerDialog picker =
colorPickerDialogFactory.create(previousColor);
picker.setListener(c ->
{
prefs.setDefaultHabitColor(c);
namePanel.setColor(c);
});
picker.show(getFragmentManager(), "picker");
}
});
}
private void setupReminderController()
{
reminderPanel.setController(new ReminderPanel.Controller()
{
@Override
public void onTimeClicked(int currentHour, int currentMin)
{
TimePickerDialog timePicker;
boolean is24HourMode = DateFormat.is24HourFormat(getContext());
timePicker =
TimePickerDialog.newInstance(reminderPanel, currentHour,
currentMin, is24HourMode);
timePicker.show(getFragmentManager(), "timePicker");
}
@Override
public void onWeekdayClicked(WeekdayList currentDays)
{
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(reminderPanel);
dialog.setSelectedDays(currentDays);
dialog.show(getChildFragmentManager(), WEEKDAY_PICKER_TAG);
}
});
}
private void restoreChildFragmentListeners()
{
final WeekdayPickerDialog dialog =
(WeekdayPickerDialog) getChildFragmentManager()
.findFragmentByTag(WEEKDAY_PICKER_TAG);
if(dialog != null) dialog.setListener(reminderPanel);
}
}

@ -1,69 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit;
import android.os.*;
import androidx.annotation.NonNull;
import org.isoron.uhabits.core.models.*;
import javax.inject.*;
import static org.isoron.uhabits.activities.habits.edit.EditHabitDialog.*;
public class EditHabitDialogFactory
{
@Inject
public EditHabitDialogFactory()
{
}
public EditHabitDialog createBoolean()
{
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putInt(BUNDLE_HABIT_TYPE, Habit.YES_NO_HABIT);
dialog.setArguments(args);
return dialog;
}
public EditHabitDialog createNumerical()
{
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putInt(BUNDLE_HABIT_TYPE, Habit.NUMBER_HABIT);
dialog.setArguments(args);
return dialog;
}
public EditHabitDialog edit(@NonNull Habit habit)
{
if (habit.getId() == null)
throw new IllegalArgumentException("habit not saved");
EditHabitDialog dialog = new EditHabitDialog();
Bundle args = new Bundle();
args.putLong(BUNDLE_HABIT_ID, habit.getId());
args.putInt(BUNDLE_HABIT_TYPE, habit.getType());
dialog.setArguments(args);
return dialog;
}
}

@ -0,0 +1,57 @@
/*
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import org.isoron.uhabits.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.intents.*
class HabitTypeDialog : AppCompatDialogFragment() {
override fun getTheme() = R.style.Translucent
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = SelectHabitTypeBinding.inflate(inflater, container, false)
binding.buttonYesNo.setOnClickListener {
val intent = IntentFactory().startEditActivity(activity!!)
startActivity(intent)
dismiss()
}
binding.buttonMeasurable.setOnClickListener {
dismiss()
}
binding.buttonSubjective.setOnClickListener{
dismiss()
}
binding.background.setOnClickListener {
dismiss()
}
return binding.root
}
}

@ -1,116 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit.views;
import android.content.*;
import android.text.*;
import android.util.*;
import android.view.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
/**
* An EditText that shows an example usage when there is no text
* currently set. The example disappears when the widget gains focus.
*/
public class ExampleEditText extends AppCompatEditText
implements View.OnFocusChangeListener
{
private String example;
private String realText;
private int color;
private int exampleColor;
private int inputType;
public ExampleEditText(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
if (attrs != null)
example = getAttribute(context, attrs, "example", "");
inputType = getInputType();
realText = getText().toString();
color = getCurrentTextColor();
init();
}
public String getRealText()
{
if(hasFocus()) return getText().toString();
else return realText;
}
@Override
public void onFocusChange(View v, boolean hasFocus)
{
if (!hasFocus) realText = getText().toString();
updateText();
}
public void setExample(String example)
{
this.example = example;
updateText();
}
public void setRealText(@NonNull String realText)
{
this.realText = realText;
updateText();
}
private void init()
{
StyledResources sr = new StyledResources(getContext());
exampleColor = sr.getColor(R.attr.mediumContrastTextColor);
setOnFocusChangeListener(this);
updateText();
}
private void updateText()
{
if (realText.isEmpty() && !isFocused())
{
setTextColor(exampleColor);
setText(example);
setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
else
{
setText(realText);
setTextColor(color);
setInputType(inputType);
}
}
}

@ -1,166 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit.views;
import android.annotation.*;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.models.*;
import butterknife.*;
import static org.isoron.uhabits.R.id.*;
public class FrequencyPanel extends FrameLayout
{
@BindView(numerator)
TextView tvNumerator;
@BindView(R.id.denominator)
TextView tvDenominator;
@BindView(R.id.spinner)
Spinner spinner;
@BindView(R.id.customFreqPanel)
ViewGroup customFreqPanel;
public FrequencyPanel(@NonNull Context context,
@Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_frequency, null);
ButterKnife.bind(this, view);
addView(view);
}
@NonNull
public Frequency getFrequency()
{
String freqNum = tvNumerator.getText().toString();
String freqDen = tvDenominator.getText().toString();
if (!freqNum.isEmpty() && !freqDen.isEmpty())
{
int numerator = Integer.parseInt(freqNum);
int denominator = Integer.parseInt(freqDen);
return new Frequency(numerator, denominator);
}
return Frequency.DAILY;
}
@SuppressLint("SetTextI18n")
public void setFrequency(@NonNull Frequency freq)
{
int position = getQuickSelectPosition(freq);
if (position >= 0) showSimplifiedFrequency(position);
else showCustomFrequency();
tvNumerator.setText(Integer.toString(freq.getNumerator()));
tvDenominator.setText(Integer.toString(freq.getDenominator()));
}
@OnItemSelected(R.id.spinner)
public void onFrequencySelected(int position)
{
if (position < 0 || position > 4) throw new IllegalArgumentException();
int freqNums[] = { 1, 1, 2, 5, 3 };
int freqDens[] = { 1, 7, 7, 7, 7 };
setFrequency(new Frequency(freqNums[position], freqDens[position]));
}
public boolean validate()
{
boolean valid = true;
Resources res = getResources();
String freqNum = tvNumerator.getText().toString();
String freqDen = tvDenominator.getText().toString();
if (freqDen.isEmpty())
{
tvDenominator.setError(
res.getString(R.string.validation_show_not_be_blank));
valid = false;
}
if (freqNum.isEmpty())
{
tvNumerator.setError(
res.getString(R.string.validation_show_not_be_blank));
valid = false;
}
if (!valid) return false;
int numerator = Integer.parseInt(freqNum);
int denominator = Integer.parseInt(freqDen);
if (numerator <= 0)
{
tvNumerator.setError(
res.getString(R.string.validation_number_should_be_positive));
valid = false;
}
if (numerator > denominator)
{
tvNumerator.setError(
res.getString(R.string.validation_at_most_one_rep_per_day));
valid = false;
}
return valid;
}
private int getQuickSelectPosition(@NonNull Frequency freq)
{
if (freq.equals(Frequency.DAILY)) return 0;
if (freq.equals(Frequency.WEEKLY)) return 1;
if (freq.equals(Frequency.TWO_TIMES_PER_WEEK)) return 2;
if (freq.equals(Frequency.FIVE_TIMES_PER_WEEK)) return 3;
return -1;
}
private void showCustomFrequency()
{
spinner.setVisibility(View.GONE);
customFreqPanel.setVisibility(View.VISIBLE);
}
private void showSimplifiedFrequency(int quickSelectPosition)
{
spinner.setVisibility(View.VISIBLE);
spinner.setSelection(quickSelectPosition);
customFreqPanel.setVisibility(View.GONE);
}
}

@ -1,164 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit.views;
import android.content.*;
import android.content.res.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class NameDescriptionPanel extends FrameLayout
{
@BindView(R.id.tvName)
EditText tvName;
@BindView(R.id.tvQuestion)
ExampleEditText tvQuestion;
@BindView(R.id.tvDescription)
ExampleEditText tvDescription;
private int color;
@NonNull
private Controller controller;
public NameDescriptionPanel(@NonNull Context context,
@Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_name, null);
ButterKnife.bind(this, view);
addView(view);
controller = new Controller() {};
}
public int getColor()
{
return color;
}
public void setColor(int color)
{
this.color = color;
tvName.setTextColor(PaletteUtils.getColor(getContext(), color));
}
@NonNull
public String getDescription()
{
return tvDescription.getRealText().trim();
}
@NonNull
public String getQuestion()
{
return tvQuestion.getRealText().trim();
}
@NonNull
public String getName()
{
return tvName.getText().toString().trim();
}
public void populateFrom(@NonNull Habit habit)
{
Resources res = getResources();
if(habit.isNumerical())
tvQuestion.setExample(res.getString(R.string.example_question_numerical));
else
tvQuestion.setExample(res.getString(R.string.example_question_boolean));
setColor(habit.getColor());
tvName.setText(habit.getName());
tvQuestion.setRealText(habit.getQuestion());
tvDescription.setRealText(habit.getDescription());
}
public boolean validate()
{
Resources res = getResources();
if (getName().isEmpty())
{
tvName.setError(
res.getString(R.string.validation_name_should_not_be_blank));
return false;
}
return true;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
setColor(bss.bundle.getInt("color"));
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("color", color);
return new BundleSavedState(superState, bundle);
}
@OnClick(R.id.buttonPickColor)
void showColorPicker()
{
controller.onColorPickerClicked(color);
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
public interface Controller
{
/**
* Called when the user has clicked the widget to select a new
* color for the habit.
*
* @param previousColor the color previously selected
*/
default void onColorPickerClicked(int previousColor) {}
}
}

@ -1,197 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit.views;
import android.content.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.datetimepicker.time.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.*;
import butterknife.*;
public class ReminderPanel extends FrameLayout
implements TimePickerDialog.OnTimeSetListener,
WeekdayPickerDialog.OnWeekdaysPickedListener
{
@BindView(R.id.tvReminderTime)
TextView tvReminderTime;
@BindView(R.id.llReminderDays)
ViewGroup llReminderDays;
@BindView(R.id.tvReminderDays)
TextView tvReminderDays;
@Nullable
private Reminder reminder;
@NonNull
private Controller controller;
public ReminderPanel(@NonNull Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_reminder, null);
ButterKnife.bind(this, view);
addView(view);
controller = new Controller() {};
setReminder(null);
}
@Nullable
public Reminder getReminder()
{
return reminder;
}
public void setReminder(@Nullable Reminder reminder)
{
this.reminder = reminder;
if (reminder == null)
{
tvReminderTime.setText(R.string.reminder_off);
llReminderDays.setVisibility(View.GONE);
return;
}
Context ctx = getContext();
String time = AndroidDateUtils.formatTime(ctx, reminder.getHour(), reminder.getMinute());
tvReminderTime.setText(time);
llReminderDays.setVisibility(View.VISIBLE);
boolean weekdays[] = reminder.getDays().toArray();
tvReminderDays.setText(AndroidDateUtils.formatWeekdayList(ctx, weekdays));
}
@Override
public void onTimeCleared(RadialPickerLayout view)
{
setReminder(null);
}
@Override
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
{
WeekdayList days = WeekdayList.EVERY_DAY;
if (reminder != null) days = reminder.getDays();
setReminder(new Reminder(hour, minute, days));
}
@Override
public void onWeekdaysSet(WeekdayList selectedDays)
{
if (reminder == null) return;
if (selectedDays.isEmpty()) selectedDays = WeekdayList.EVERY_DAY;
setReminder(new Reminder(reminder.getHour(), reminder.getMinute(),
selectedDays));
}
public void setController(@NonNull Controller controller)
{
this.controller = controller;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
BundleSavedState bss = (BundleSavedState) state;
if (!bss.bundle.isEmpty())
{
int days = bss.bundle.getInt("days");
int hour = bss.bundle.getInt("hour");
int minute = bss.bundle.getInt("minute");
reminder = new Reminder(hour, minute, new WeekdayList(days));
setReminder(reminder);
}
super.onRestoreInstanceState(bss.getSuperState());
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
if (reminder != null)
{
bundle.putInt("days", reminder.getDays().toInteger());
bundle.putInt("hour", reminder.getHour());
bundle.putInt("minute", reminder.getMinute());
}
return new BundleSavedState(superState, bundle);
}
@OnClick(R.id.tvReminderTime)
void onDateSpinnerClick()
{
int hour = 8;
int min = 0;
if (reminder != null)
{
hour = reminder.getHour();
min = reminder.getMinute();
}
controller.onTimeClicked(hour, min);
}
@OnClick(R.id.tvReminderDays)
void onWeekdayClicked()
{
if (reminder == null) return;
controller.onWeekdayClicked(reminder.getDays());
}
public interface Controller
{
/**
* Called when the user has clicked the widget to change the time of
* the reminder.
*
* @param currentHour hour previously picked by the user
* @param currentMin minute previously picked by the user
*/
default void onTimeClicked(int currentHour, int currentMin) {}
/**
* Called when the used has clicked the widget to change the days
* of the reminder.
*
* @param currentDays days previously selected by the user.
*/
default void onWeekdayClicked(WeekdayList currentDays) {}
}
}

@ -1,93 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.edit.views;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.isoron.uhabits.R;
import java.text.DecimalFormat;
import butterknife.*;
public class TargetPanel extends FrameLayout
{
private DecimalFormat valueFormatter = new DecimalFormat("#.##");
@BindView(R.id.tvUnit)
ExampleEditText tvUnit;
@BindView(R.id.tvTargetCount)
TextView tvTargetValue;
public TargetPanel(@NonNull Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
View view = inflate(context, R.layout.edit_habit_target, null);
ButterKnife.bind(this, view);
addView(view);
}
public double getTargetValue()
{
String sValue = tvTargetValue.getText().toString();
return Double.parseDouble(sValue);
}
public void setTargetValue(double targetValue)
{
tvTargetValue.setText(valueFormatter.format(targetValue));
}
public String getUnit()
{
return tvUnit.getRealText();
}
public void setUnit(String unit)
{
tvUnit.setRealText(unit);
}
public boolean validate()
{
Resources res = getResources();
String sValue = tvTargetValue.getText().toString();
double value = Double.parseDouble(sValue);
if (value <= 0)
{
tvTargetValue.setError(
res.getString(R.string.validation_number_should_be_positive));
return false;
}
return true;
}
}

@ -39,9 +39,7 @@ class ListHabitsMenu @Inject constructor(
val nightModeItem = menu.findItem(R.id.actionToggleNightMode) val nightModeItem = menu.findItem(R.id.actionToggleNightMode)
val hideArchivedItem = menu.findItem(R.id.actionHideArchived) val hideArchivedItem = menu.findItem(R.id.actionHideArchived)
val hideCompletedItem = menu.findItem(R.id.actionHideCompleted) val hideCompletedItem = menu.findItem(R.id.actionHideCompleted)
val addNumericalHabit = menu.findItem(R.id.actionCreateNumeralHabit)
addNumericalHabit.isVisible = preferences.isDeveloper
nightModeItem.isChecked = themeSwitcher.isNightMode nightModeItem.isChecked = themeSwitcher.isNightMode
hideArchivedItem.isChecked = !preferences.showArchived hideArchivedItem.isChecked = !preferences.showArchived
hideCompletedItem.isChecked = !preferences.showCompleted hideCompletedItem.isChecked = !preferences.showCompleted
@ -54,13 +52,8 @@ class ListHabitsMenu @Inject constructor(
return true return true
} }
R.id.actionCreateBooleanHabit -> { R.id.actionCreateHabit -> {
behavior.onCreateBooleanHabit() behavior.onCreateHabit()
return true
}
R.id.actionCreateNumeralHabit -> {
behavior.onCreateNumericalHabit()
return true return true
} }

@ -58,14 +58,12 @@ class ListHabitsScreen
private val commandRunner: CommandRunner, private val commandRunner: CommandRunner,
private val intentFactory: IntentFactory, private val intentFactory: IntentFactory,
private val themeSwitcher: ThemeSwitcher, private val themeSwitcher: ThemeSwitcher,
private val preferences: Preferences,
private val adapter: HabitCardListAdapter, private val adapter: HabitCardListAdapter,
private val taskRunner: TaskRunner, private val taskRunner: TaskRunner,
private val exportDBFactory: ExportDBTaskFactory, private val exportDBFactory: ExportDBTaskFactory,
private val importTaskFactory: ImportDataTaskFactory, private val importTaskFactory: ImportDataTaskFactory,
private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory, private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory,
private val colorPickerFactory: ColorPickerDialogFactory, private val colorPickerFactory: ColorPickerDialogFactory,
private val editHabitDialogFactory: EditHabitDialogFactory,
private val numberPickerFactory: NumberPickerFactory, private val numberPickerFactory: NumberPickerFactory,
private val behavior: Lazy<ListHabitsBehavior>, private val behavior: Lazy<ListHabitsBehavior>,
private val menu: Lazy<ListHabitsMenu>, private val menu: Lazy<ListHabitsMenu>,
@ -137,14 +135,9 @@ class ListHabitsScreen
activity.startActivity(intent) activity.startActivity(intent)
} }
override fun showCreateBooleanHabitScreen() { override fun showSelectHabitTypeDialog() {
val dialog = editHabitDialogFactory.createBoolean() val dialog = HabitTypeDialog()
activity.showDialog(dialog, "editHabit") activity.showDialog(dialog, "habitType")
}
override fun showCreateNumericalHabitScreen() {
val dialog = editHabitDialogFactory.createNumerical()
activity.showDialog(dialog, "editHabit")
} }
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) { override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
@ -152,8 +145,8 @@ class ListHabitsScreen
} }
override fun showEditHabitsScreen(habits: List<Habit>) { override fun showEditHabitsScreen(habits: List<Habit>) {
val dialog = editHabitDialogFactory.edit(habits[0]) val intent = intentFactory.startEditActivity(activity!!, habits[0])
activity.showDialog(dialog, "editNumericalHabit") activity.startActivity(intent)
} }
override fun showFAQScreen() { override fun showFAQScreen() {

@ -19,6 +19,8 @@
package org.isoron.uhabits.activities.habits.show; package org.isoron.uhabits.activities.habits.show;
import android.content.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.isoron.androidbase.activities.*; import org.isoron.androidbase.activities.*;
@ -28,6 +30,7 @@ import org.isoron.uhabits.activities.habits.edit.*;
import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.ui.callbacks.*; import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.ui.screens.habits.show.*; import org.isoron.uhabits.core.ui.screens.habits.show.*;
import org.isoron.uhabits.intents.*;
import javax.inject.*; import javax.inject.*;
@ -43,30 +46,30 @@ public class ShowHabitScreen extends BaseScreen
@NonNull @NonNull
private final Habit habit; private final Habit habit;
@NonNull
private final EditHabitDialogFactory editHabitDialogFactory;
@NonNull @NonNull
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory; private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
private final Lazy<ShowHabitBehavior> behavior; private final Lazy<ShowHabitBehavior> behavior;
@NonNull
private final IntentFactory intentFactory;
@Inject @Inject
public ShowHabitScreen(@NonNull BaseActivity activity, public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit, @NonNull Habit habit,
@NonNull ShowHabitRootView view, @NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu, @NonNull ShowHabitsMenu menu,
@NonNull EditHabitDialogFactory editHabitDialogFactory,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull IntentFactory intentFactory,
@NonNull Lazy<ShowHabitBehavior> behavior) @NonNull Lazy<ShowHabitBehavior> behavior)
{ {
super(activity); super(activity);
this.intentFactory = intentFactory;
setMenu(menu); setMenu(menu);
setRootView(view); setRootView(view);
this.habit = habit; this.habit = habit;
this.behavior = behavior; this.behavior = behavior;
this.editHabitDialogFactory = editHabitDialogFactory;
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory; this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
view.setController(this); view.setController(this);
} }
@ -102,7 +105,8 @@ public class ShowHabitScreen extends BaseScreen
@Override @Override
public void showEditHabitScreen(@NonNull Habit habit) public void showEditHabitScreen(@NonNull Habit habit)
{ {
activity.showDialog(editHabitDialogFactory.edit(habit), "editHabit"); Intent intent = intentFactory.startEditActivity(activity, habit);
activity.startActivity(intent);
} }
@Override @Override

@ -21,8 +21,10 @@ package org.isoron.uhabits.intents
import android.content.* import android.content.*
import android.net.* import android.net.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.* import org.isoron.uhabits.*
import org.isoron.uhabits.activities.about.* import org.isoron.uhabits.activities.about.*
import org.isoron.uhabits.activities.habits.edit.*
import org.isoron.uhabits.activities.habits.show.* import org.isoron.uhabits.activities.habits.show.*
import org.isoron.uhabits.activities.intro.* import org.isoron.uhabits.activities.intro.*
import org.isoron.uhabits.activities.settings.* import org.isoron.uhabits.activities.settings.*
@ -81,4 +83,14 @@ class IntentFactory
fun codeContributors(context: Context) = fun codeContributors(context: Context) =
buildViewIntent(context.getString(R.string.codeContributorsURL)) buildViewIntent(context.getString(R.string.codeContributorsURL))
fun startEditActivity(context: Context): Intent {
return Intent(context, EditHabitActivity::class.java)
}
fun startEditActivity(context: Context, habit: Habit): Intent {
val intent = startEditActivity(context)
intent.putExtra("habitId", habit.id)
return intent
}
} }

@ -2,6 +2,7 @@ package org.isoron.uhabits.notifications;
import android.app.*; import android.app.*;
import android.graphics.*;
import android.os.*; import android.os.*;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -66,7 +67,8 @@ public class SnoozeDelayPickerActivity extends FragmentActivity
}, },
calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE), calendar.get(Calendar.MINUTE),
DateFormat.is24HourFormat(this)); DateFormat.is24HourFormat(this),
Color.BLUE);
dialog.show(getSupportFragmentManager(), "timePicker"); dialog.show(getSupportFragmentManager(), "timePicker");
} }

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/highContrastReverseTextColor"/>
<stroke android:width="1dp" android:color="?attr/lowContrastTextColor" />
<corners android:radius="4dp"/>
<padding
android:left="8dp"
android:top="8dp"
android:right="8dp"
android:bottom="8dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/highContrastReverseTextColor"/>
<stroke android:width="1dp" android:color="?attr/lowContrastTextColor" />
<corners android:radius="4dp"/>
<padding
android:left="0dp"
android:top="8dp"
android:right="0dp"
android:bottom="0dp" />
</shape>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/mediumContrastTextColor"
android:pathData="M7,10l5,5 5,-5z"/>
</vector>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?colorAccent">
<item android:gravity="center">
<shape android:shape="rectangle">
<solid android:color="?cardBgColor"/>
<corners android:radius="5dp" />
</shape>
</item>
</ripple>

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/highContrastReverseTextColor"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".activities.habits.edit.EditHabitActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:elevation="2dp"
android:gravity="end"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/create_habit"
app:titleTextColor="@color/white">
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonSave"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="16dp"
android:text="@string/save"
android:textColor="@color/white"
app:rippleColor="@color/white"
app:strokeColor="@color/white" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp">
<!-- Title and color -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- Habit Title -->
<FrameLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/name" />
<EditText
android:id="@+id/nameInput"
style="@style/FormInput"
android:maxLines="1"
android:inputType="textCapSentences"
android:hint="@string/exercise_habit_name"
/>
</LinearLayout>
</FrameLayout>
<!-- Habit Color -->
<FrameLayout
android:layout_width="80dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingStart="0dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="Color" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/colorButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:backgroundTint="#E23673" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
<!-- Habit Question -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingTop="4dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/question" />
<EditText
android:id="@+id/questionInput"
style="@style/FormInput"
android:inputType="textCapSentences|textMultiLine"
android:hint="@string/example_question_boolean"
/>
</LinearLayout>
</FrameLayout>
<!-- Frequency -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
android:paddingTop="4dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/frequency" />
<TextView
style="@style/FormDropdown"
android:id="@+id/frequencyPicker"
android:textColor="?attr/highContrastTextColor"
/>
</LinearLayout>
</FrameLayout>
<!-- Reminder -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/reminder" />
<TextView
style="@style/FormDropdown"
android:id="@+id/reminderTimePicker"
android:text="@string/reminder_off" />
<View
style="@style/FormDivider"
android:id="@+id/reminderDivider"/>
<TextView
style="@style/FormDropdown"
android:id="@+id/reminderDatePicker"
android:text="" />
</LinearLayout>
</FrameLayout>
<!-- Notes -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/notes" />
<EditText
android:id="@+id/notesInput"
style="@style/FormInput"
android:inputType="textCapSentences|textMultiLine"
android:hint="@string/example_notes" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -1,82 +0,0 @@
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/container"
style="@style/dialogForm"
tools:context=".activities.habits.edit.EditHabitDialog"
tools:ignore="MergeRootFrame">
<LinearLayout
android:id="@+id/formPanel"
style="@style/dialogFormPanel">
<org.isoron.uhabits.activities.habits.edit.views.NameDescriptionPanel
android:id="@+id/namePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.FrequencyPanel
android:id="@+id/frequencyPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.TargetPanel
android:id="@+id/targetPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.isoron.uhabits.activities.habits.edit.views.ReminderPanel
android:id="@+id/reminderPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingEnd="16dp"
android:paddingRight="16dp">
<Button
android:id="@+id/buttonDiscard"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/discard" />
<Button
android:id="@+id/buttonSave"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save" />
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout style="@style/dialogFormRow"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<TextView
style="@style/dialogFormLabel"
android:text="@string/repeat"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:entries="@array/frequencyQuickSelect"
android:minWidth="400dp"
android:theme="@style/dialogFormText"
android:visibility="gone"/>
<org.apmem.tools.layouts.FlowLayout
android:id="@+id/customFreqPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="fill"
android:visibility="visible">
<EditText
android:id="@+id/numerator"
style="@style/dialogFormInputLargeNumber"/>
<TextView
style="@style/dialogFormText"
android:gravity="center"
android:text="@string/times_every"/>
<EditText
android:id="@+id/denominator"
style="@style/dialogFormInputLargeNumber"/>
<TextView
style="@style/dialogFormText"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:text="@string/days"/>
</org.apmem.tools.layouts.FlowLayout>
</LinearLayout>

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://isoron.org/android"
xmlns:app1="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="300dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilName"
android:layout_width="0dp"
android:layout_height="wrap_content"
app1:layout_constraintEnd_toStartOf="@+id/buttonPickColor"
app1:layout_constraintHorizontal_weight="6"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toTopOf="parent">
<EditText
android:id="@+id/tvName"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="start"
android:gravity="center_vertical"
android:hint="@string/name">
<requestFocus />
</EditText>
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/buttonPickColor"
style="@style/dialogFormInputColor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/color_picker_default_title"
android:src="?dialogIconChangeColor"
app1:layout_constraintBottom_toBottomOf="@id/tilName"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintHorizontal_weight="1"
app1:layout_constraintStart_toEndOf="@+id/tilName"
app1:layout_constraintTop_toTopOf="@id/tilName" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilQuestion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/question"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toBottomOf="@id/tilName">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvQuestion"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:example="@string/example_question_numerical" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/notes"
app1:layout_constraintBottom_toBottomOf="parent"
app1:layout_constraintEnd_toEndOf="parent"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toBottomOf="@id/tilQuestion">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvDescription"
style="@style/dialogFormInputMultiline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
app:example="@string/example_notes" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
style="@style/dialogFormRow"
android:layout_marginTop="12dp">
<TextView
style="@style/dialogFormLabel"
android:text="@string/reminder"/>
<TextView
android:id="@+id/tvReminderTime"
style="@style/dialogFormSpinner"/>
</LinearLayout>
<LinearLayout
android:id="@+id/llReminderDays"
style="@style/dialogFormRow"
android:layout_marginTop="12dp">
<TextView
style="@style/dialogFormLabel"
android:text=""/>
<TextView
android:id="@+id/tvReminderDays"
style="@style/dialogFormSpinner"/>
</LinearLayout>
</LinearLayout>

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout style="@style/dialogFormRow"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://isoron.org/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
style="@style/dialogFormLabel"
android:text="@string/target"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2">
<EditText
android:id="@+id/tvTargetCount"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/count"
android:inputType="numberDecimal"
android:text="@string/default_count"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2">
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvUnit"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/unit"
android:inputType="text"
app:example="@string/example_units"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:paddingTop="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/everyDayRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/every_day" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/everyXDaysRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Every" />
<EditText
android:id="@+id/everyXDaysTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="days" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/xTimesPerWeekRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/xTimesPerWeekTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="times per week" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/xTimesPerMonthRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/xTimesPerMonthTextView"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:background="@drawable/bg_input_box"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:inputType="number"
android:maxLength="2"
android:text="10" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="times per month" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#a0000000"
android:clickable="true"
android:id="@+id/background"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonYesNo"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/yes_or_no" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/yes_or_no_example" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonMeasurable"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/measurable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/measurable_example" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonSubjective"
style="@style/SelectHabitTypeButton">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonTitle"
android:text="@string/subjective" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SelectHabitTypeButtonBody"
android:text="@string/subjective_example" />
</LinearLayout>
</LinearLayout>

@ -23,7 +23,7 @@
tools:context=".MainActivity"> tools:context=".MainActivity">
<item <item
android:id="@+id/actionCreateBooleanHabit" android:id="@+id/actionCreateHabit"
android:enabled="true" android:enabled="true"
android:icon="?iconAdd" android:icon="?iconAdd"
android:title="@string/add_habit" android:title="@string/add_habit"
@ -96,11 +96,4 @@
android:title="@string/about" android:title="@string/about"
app:showAsAction="never"/> app:showAsAction="never"/>
<item
android:id="@+id/actionCreateNumeralHabit"
android:enabled="true"
android:title="Add Numerical Habit"
android:orderInCategory="200"
app:showAsAction="never" />
</menu> </menu>

@ -245,6 +245,15 @@
<string name="first_day_of_the_week">First day of the week</string> <string name="first_day_of_the_week">First day of the week</string>
<string name="default_reminder_question">Have you completed this habit today?</string> <string name="default_reminder_question">Have you completed this habit today?</string>
<string name="notes">Notes</string> <string name="notes">Notes</string>
<string name="example_notes">You can put whatever you want here!</string> <string name="example_notes">(Optional)</string>
<string name="yes_or_no_example">e.g. Did you wake up early today? Did you exercise? Did you play chess?</string>
<string name="measurable">Measurable</string>
<string name="measurable_example">e.g. How many miles did you run today? How many pages did you read? How many calories did you eat?</string>
<string name="subjective">Subjective</string>
<string name="subjective_example">e.g. Are you felling happy today? Definitely, somewhat, not at all? How frequently did you snack? Very often, sometimes, never?</string>
<string name="x_times_per_week">%d times per week</string>
<string name="x_times_per_month">%d times per month</string>
<string name="exercise_habit_name">e.g. Exercise</string>
</resources> </resources>

@ -18,10 +18,11 @@
--> -->
<resources> <resources>
<style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light.NoActionBar"> <style name="AppBaseTheme" parent="@style/Theme.MaterialComponents.Light.NoActionBar">
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="android:dialogTheme">@style/Theme.AppCompat.Light.Dialog</item> <item name="android:dialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
<item name="android:alertDialogTheme">@style/Theme.AppCompat.Light.Dialog</item> <item name="android:alertDialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
<item name="android:navigationBarColor">?attr/colorPrimary</item>
<item name="windowActionModeOverlay">true</item> <item name="windowActionModeOverlay">true</item>
<item name="actionModeBackground">@color/blue_grey_700</item> <item name="actionModeBackground">@color/blue_grey_700</item>
@ -30,9 +31,9 @@
<item name="selectedBackground">@drawable/selected_box_light</item> <item name="selectedBackground">@drawable/selected_box_light</item>
<item name="cardBackground">@drawable/card_light_background</item> <item name="cardBackground">@drawable/card_light_background</item>
<item name="colorPrimary">@color/blue_grey_800</item> <item name="colorPrimary">#363636</item>
<item name="colorPrimaryDark">@color/blue_grey_900</item> <item name="colorPrimaryDark">#303030</item>
<item name="colorAccent">?aboutScreenColor</item> <item name="colorAccent">#303030</item>
<item name="cardBgColor">@color/grey_50</item> <item name="cardBgColor">@color/grey_50</item>
<item name="windowBackgroundColor">@color/grey_200</item> <item name="windowBackgroundColor">@color/grey_200</item>
<item name="headerBackgroundColor">@color/grey_200</item> <item name="headerBackgroundColor">@color/grey_200</item>
@ -74,7 +75,7 @@
<item name="android:textSize">@dimen/smallTextSize</item> <item name="android:textSize">@dimen/smallTextSize</item>
</style> </style>
<style name="AppBaseThemeDark" parent="@style/Theme.AppCompat.NoActionBar"> <style name="AppBaseThemeDark" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="android:dialogTheme">@style/Theme.AppCompat.Dialog</item> <item name="android:dialogTheme">@style/Theme.AppCompat.Dialog</item>
<item name="android:alertDialogTheme">@style/Theme.AppCompat.Dialog</item> <item name="android:alertDialogTheme">@style/Theme.AppCompat.Dialog</item>
@ -256,4 +257,86 @@
<style name="ScrollableRecyclerViewStyle" parent="android:Widget"> <style name="ScrollableRecyclerViewStyle" parent="android:Widget">
<item name="android:scrollbars">vertical</item> <item name="android:scrollbars">vertical</item>
</style> </style>
<style name="SelectHabitTypeButton">
<item name="android:orientation">vertical</item>
<item name="android:paddingTop">14dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginRight">16dp</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:elevation">6dp</item>
<item name="android:background">@drawable/round_ripple</item>
<item name="android:clickable">true</item>
<item name="android:selectable">true</item>
</style>
<style name="SelectHabitTypeButtonTitle">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="SelectHabitTypeButtonBody">
<item name="android:textSize">@dimen/smallTextSize</item>
<item name="android:lineSpacingMultiplier">1.25</item>
</style>
<style name="Translucent">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
<style name="FormLabel">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginTop">-17dp</item>
<item name="android:layout_marginBottom">-4dp</item>
<item name="android:background">?attr/highContrastReverseTextColor</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:textSize">@dimen/smallerTextSize</item>
</style>
<style name="FormInput">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@color/transparent</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
<item name="android:paddingTop">16dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:textSize">@dimen/regularTextSize</item>
</style>
<style name="FormDropdown">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:drawableEnd">@drawable/ic_arrow_drop_down_dark</item>
<item name="android:padding">16dp</item>
<item name="android:text">@string/every_day</item>
<item name="android:textSize">@dimen/regularTextSize</item>
</style>
<style name="FormInnerBox">
<item name="android:background">@drawable/bg_input_group</item>
<item name="android:clipChildren">false</item>
<item name="android:clipToPadding">false</item>
<item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
</style>
<style name="FormDivider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>
<item name="android:background">?attr/lowContrastTextColor</item>
</style>
</resources> </resources>

@ -61,14 +61,9 @@ public class ListHabitsMenuBehavior
updateAdapterFilter(); updateAdapterFilter();
} }
public void onCreateBooleanHabit() public void onCreateHabit()
{ {
screen.showCreateBooleanHabitScreen(); screen.showSelectHabitTypeDialog();
}
public void onCreateNumericalHabit()
{
screen.showCreateNumericalHabitScreen();
} }
public void onViewFAQ() public void onViewFAQ()
@ -150,12 +145,10 @@ public class ListHabitsMenuBehavior
void showAboutScreen(); void showAboutScreen();
void showCreateBooleanHabitScreen();
void showCreateNumericalHabitScreen();
void showFAQScreen(); void showFAQScreen();
void showSettingsScreen(); void showSettingsScreen();
void showSelectHabitTypeDialog();
} }
} }

@ -112,6 +112,7 @@ public class ListHabitsSelectionMenuBehavior
public void onEditHabits() public void onEditHabits()
{ {
screen.showEditHabitsScreen(adapter.getSelected()); screen.showEditHabitsScreen(adapter.getSelected());
adapter.clearSelection();
} }
public void onUnarchiveHabits() public void onUnarchiveHabits()

Loading…
Cancel
Save