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
node_modules
*xcuserdata*
*.sketch
/design
/releases
/screenshots

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

@ -84,6 +84,14 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private AnimatorSet mTransition;
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 {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}

@ -40,7 +40,7 @@ public class RadialSelectorView extends View {
// Alpha level for the line.
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 mDrawValuesReady;
@ -96,8 +96,6 @@ public class RadialSelectorView extends View {
Resources res = context.getResources();
int blue = res.getColor(R.color.blue);
mPaint.setColor(blue);
mPaint.setAntiAlias(true);
mSelectionAlpha = SELECTED_ALPHA;
@ -139,15 +137,11 @@ public class RadialSelectorView extends View {
/* package */ void setTheme(Context context, boolean themeDark) {
Resources res = context.getResources();
int color;
if (themeDark) {
color = res.getColor(R.color.red);
mSelectionAlpha = SELECTED_ALPHA_THEME_DARK;
} else {
color = res.getColor(R.color.blue);
mSelectionAlpha = SELECTED_ALPHA;
}
mPaint.setColor(color);
}
/**

@ -23,7 +23,9 @@ import android.app.*;
import android.content.*;
import android.content.res.*;
import android.os.*;
import androidx.appcompat.app.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
@ -39,7 +41,8 @@ import java.util.*;
/**
* 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 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_TYPED_TIMES = "typed_times";
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 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 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 minute The minute that was set.
* @param minute The minute that was set.
*/
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.
}
@SuppressLint("Java")
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.
}
public static TimePickerDialog newInstance(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
TimePickerDialog ret = new TimePickerDialog();
ret.initialize(callback, hourOfDay, minute, is24HourMode);
ret.initialize(callback, hourOfDay, minute, is24HourMode, color);
return ret;
}
public void initialize(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
mCallback = callback;
mInitialHourOfDay = hourOfDay;
@ -146,40 +163,47 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mIs24HourMode = is24HourMode;
mInKbMode = false;
mThemeDark = false;
mSelectedColor = color;
}
/**
* 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;
}
public boolean isThemeDark() {
public boolean isThemeDark()
{
return mThemeDark;
}
public void setOnTimeSetListener(OnTimeSetListener callback) {
public void setOnTimeSetListener(OnTimeSetListener callback)
{
mCallback = callback;
}
public void setStartTime(int hourOfDay, int minute) {
public void setStartTime(int hourOfDay, int minute)
{
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
mInKbMode = false;
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY)
&& savedInstanceState.containsKey(KEY_MINUTE)
&& savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
&& savedInstanceState.containsKey(KEY_MINUTE)
&& savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY);
mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME);
mSelectedColor = savedInstanceState.getInt(KEY_SELECTED_COLOR);
}
}
@ -191,7 +215,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle savedInstanceState)
{
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
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);
mMinutePickerDescription = res.getString(R.string.minute_picker_description);
mSelectMinutes = res.getString(R.string.select_minutes);
mSelectedColor = res.getColor(mThemeDark? R.color.red : R.color.blue);
mUnselectedColor = res.getColor(mThemeDark? R.color.white : R.color.numbers_text_color);
//mSelectedColor = res.getColor(mThemeDark ? R.color.red : R.color.blue);
mUnselectedColor = res.getColor(mThemeDark ? R.color.white : R.color.numbers_text_color);
mHourView = (TextView) view.findViewById(R.id.hours);
mHourView.setOnKeyListener(keyboardListener);
@ -223,8 +248,9 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker);
mTimePicker.setOnValueSelectedListener(this);
mTimePicker.setOnKeyListener(keyboardListener);
mTimePicker.setColor(mSelectedColor);
mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay,
mInitialMinute, mIs24HourMode);
mInitialMinute, mIs24HourMode);
int currentItemShowing = HOUR_INDEX;
if (savedInstanceState != null &&
@ -234,25 +260,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
setCurrentItemShowing(currentItemShowing, false, true, true);
mTimePicker.invalidate();
mHourView.setOnClickListener(new OnClickListener() {
mHourView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
setCurrentItemShowing(HOUR_INDEX, true, false, true);
tryVibrate();
}
});
mMinuteView.setOnClickListener(new OnClickListener() {
mMinuteView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
setCurrentItemShowing(MINUTE_INDEX, true, false, true);
tryVibrate();
}
});
mDoneButton = (TextView) view.findViewById(R.id.done_button);
mDoneButton.setOnClickListener(new OnClickListener() {
mDoneButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
if (mInKbMode && isTypedTimeFullyLegal()) {
finishKbMode(false);
} else {
@ -260,7 +292,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
if (mCallback != null) {
mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes());
mTimePicker.getHours(), mTimePicker.getMinutes());
}
dismiss();
}
@ -269,16 +301,16 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mClearButton = (TextView) view.findViewById(R.id.clear_button);
mClearButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if(mCallback != null) {
mCallback.onTimeCleared(mTimePicker);
}
dismiss();
}
});
{
@Override
public void onClick(View v)
{
if (mCallback != null) {
mCallback.onTimeCleared(mTimePicker);
}
dismiss();
}
});
mClearButton.setOnKeyListener(keyboardListener);
// Enable or disable the AM/PM view.
@ -293,15 +325,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
separatorView.setLayoutParams(paramsSeparator);
} else {
mAmPmTextView.setVisibility(View.VISIBLE);
updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM);
mAmPmHitspace.setOnClickListener(new OnClickListener() {
updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM);
mAmPmHitspace.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
tryVibrate();
int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
if (amOrPm == AM) {
amOrPm = PM;
} else if (amOrPm == PM){
} else if (amOrPm == PM) {
amOrPm = AM;
}
updateAmPmDisplay(amOrPm);
@ -328,56 +362,61 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
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);
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 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);
// 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.
view.findViewById(R.id.time_display_background).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.ampm_label)).setTextColor(mThemeDark? white : timeDisplay);
view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line);
mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor);
mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground);
// view.findViewById(R.id.time_display_background).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.ampm_label)).setTextColor(mThemeDark? white : timeDisplay);
// view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line);
// mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor);
// mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
// mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground);
return view;
}
@Override
public void onResume() {
public void onResume()
{
super.onResume();
mHapticFeedbackController.start();
}
@Override
public void onPause() {
public void onPause()
{
super.onPause();
mHapticFeedbackController.stop();
}
public void tryVibrate() {
public void tryVibrate()
{
mHapticFeedbackController.tryVibrate();
}
private void updateAmPmDisplay(int amOrPm) {
private void updateAmPmDisplay(int amOrPm)
{
if (amOrPm == AM) {
mAmPmTextView.setText(mAmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mAmText);
mAmPmHitspace.setContentDescription(mAmText);
} else if (amOrPm == PM){
} else if (amOrPm == PM) {
mAmPmTextView.setText(mPmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mPmText);
mAmPmHitspace.setContentDescription(mPmText);
@ -387,7 +426,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
@Override
public void onSaveInstanceState(Bundle outState) {
public void onSaveInstanceState(Bundle outState)
{
if (mTimePicker != null) {
outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours());
outState.putInt(KEY_MINUTE, mTimePicker.getMinutes());
@ -398,6 +438,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
}
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.
*/
@Override
public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance)
{
if (pickerIndex == HOUR_INDEX) {
setHour(newValue, false);
String announcement = String.format("%d", newValue);
@ -417,7 +459,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
} else if (pickerIndex == MINUTE_INDEX){
} else if (pickerIndex == MINUTE_INDEX) {
setMinute(newValue);
mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue);
} 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;
if (mIs24HourMode) {
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) {
value = 0;
}
@ -462,7 +506,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
// Show either Hours or Minutes.
private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
boolean announce) {
boolean announce)
{
mTimePicker.setCurrentItemShowing(index, animateCircle);
TextView labelToAnimate;
@ -485,8 +530,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
labelToAnimate = mMinuteView;
}
int hourColor = (index == HOUR_INDEX)? mSelectedColor : mUnselectedColor;
int minuteColor = (index == MINUTE_INDEX)? mSelectedColor : mUnselectedColor;
int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor;
int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor;
mHourView.setTextColor(hourColor);
mMinuteView.setTextColor(minuteColor);
@ -499,15 +544,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* For keyboard mode, processes key events.
*
* @param keyCode the pressed key.
* @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) {
dismiss();
return true;
} else if (keyCode == KeyEvent.KEYCODE_TAB) {
if(mInKbMode) {
if (mInKbMode) {
if (isTypedTimeFullyLegal()) {
finishKbMode(true);
}
@ -522,7 +569,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
if (mCallback != null) {
mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes());
mTimePicker.getHours(), mTimePicker.getMinutes());
}
dismiss();
return true;
@ -539,7 +586,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
}
Utils.tryAccessibilityAnnounce(mTimePicker,
String.format(mDeletedKeyFormat, deletedKeyStr));
String.format(mDeletedKeyFormat, deletedKeyStr));
updateDisplay(true);
}
}
@ -549,7 +596,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
|| keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
|| keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
|| (!mIs24HourMode &&
(keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
(keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
if (!mInKbMode) {
if (mTimePicker == 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
* middle of a touch-event.
*
* @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.
* key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
* key.
*/
private void tryStartingKbMode(int keyCode) {
private void tryStartingKbMode(int keyCode)
{
if (mTimePicker.trySettingInputEnabled(false) &&
(keyCode == -1 || addKeyIfLegal(keyCode))) {
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,
// we'll need to see if AM/PM have been typed.
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,
* or may become legal as more keys are typed (excluding backspace).
*/
private boolean isTypedTimeLegalSoFar() {
private boolean isTypedTimeLegalSoFar()
{
Node node = mLegalTimesTree;
for (int keyCode : mTypedTimes) {
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.
*/
private boolean isTypedTimeFullyLegal() {
private boolean isTypedTimeFullyLegal()
{
if (mIs24HourMode) {
// 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.
@ -645,7 +697,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private int deleteLastTypedKey() {
private int deleteLastTypedKey()
{
int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
if (!isTypedTimeFullyLegal()) {
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.
*
* @param changeDisplays If true, update the displays with the relevant time.
*/
private void finishKbMode(boolean updateDisplays) {
private void finishKbMode(boolean updateDisplays)
{
mInKbMode = false;
if (!mTypedTimes.isEmpty()) {
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
* empty, either show an empty display (filled with the placeholder text), or update from the
* timepicker's values.
*
* @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()) {
int hour = mTimePicker.getHours();
int minute = mTimePicker.getMinutes();
setHour(hour, true);
setMinute(minute);
if (!mIs24HourMode) {
updateAmPmDisplay(hour < 12? AM : PM);
updateAmPmDisplay(hour < 12 ? AM : PM);
}
setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true);
mDoneButton.setEnabled(true);
} else {
Boolean[] enteredZeros = {false, false};
int[] values = getEnteredTime(enteredZeros);
String hourFormat = enteredZeros[0]? "%02d" : "%2d";
String minuteFormat = (enteredZeros[1])? "%02d" : "%2d";
String hourStr = (values[0] == -1)? mDoublePlaceholderText :
String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
String minuteStr = (values[1] == -1)? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
mHourView.setText(hourStr);
mHourSpaceView.setText(hourStr);
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) {
case KeyEvent.KEYCODE_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.
*
* @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
* hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
* 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.
* @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.
*/
private int[] getEnteredTime(Boolean[] enteredZeros) {
private int[] getEnteredTime(Boolean[] enteredZeros)
{
int amOrPm = -1;
int startIndex = 1;
if (!mIs24HourMode && isTypedTimeFullyLegal()) {
int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
if (keyCode == getAmOrPmKeyCode(AM)) {
amOrPm = AM;
} else if (keyCode == getAmOrPmKeyCode(PM)){
} else if (keyCode == getAmOrPmKeyCode(PM)) {
amOrPm = PM;
}
startIndex = 2;
@ -765,15 +825,15 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
if (i == startIndex) {
minute = val;
} else if (i == startIndex+1) {
minute += 10*val;
} else if (i == startIndex + 1) {
minute += 10 * val;
if (enteredZeros != null && val == 0) {
enteredZeros[1] = true;
}
} else if (i == startIndex+2) {
} else if (i == startIndex + 2) {
hour = val;
} else if (i == startIndex+3) {
hour += 10*val;
} else if (i == startIndex + 3) {
hour += 10 * val;
if (enteredZeros != null && val == 0) {
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.
*/
private int getAmOrPmKeyCode(int amOrPm) {
private int getAmOrPmKeyCode(int amOrPm)
{
// Cache the codes.
if (mAmKeyCode == -1 || mPmKeyCode == -1) {
// 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.
*/
private void generateLegalTimesTree() {
private void generateLegalTimesTree()
{
// Create a quick cache of numbers to their keycodes.
int k0 = KeyEvent.KEYCODE_0;
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.
* mChildren are the children that can be reached from this node.
*/
private class Node {
private class Node
{
private int[] mLegalKeys;
private ArrayList<Node> mChildren;
public Node(int... legalKeys) {
public Node(int... legalKeys)
{
mLegalKeys = legalKeys;
mChildren = new ArrayList<Node>();
}
public void addChild(Node child) {
public void addChild(Node child)
{
mChildren.add(child);
}
public boolean containsKey(int key) {
public boolean containsKey(int key)
{
for (int i = 0; i < mLegalKeys.length; i++) {
if (mLegalKeys[i] == key) {
return true;
@ -977,7 +1043,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
return false;
}
public Node canReach(int key) {
public Node canReach(int key)
{
if (mChildren == 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
public boolean onKey(View v, int keyCode, KeyEvent event) {
public boolean onKey(View v, int keyCode, KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_UP) {
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;
}
@Override
public void onDismiss(DialogInterface dialog) {
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
if( dismissListener != null )
if (dismissListener != null)
dismissListener.onDismiss(dialog);
}
}

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

@ -76,7 +76,7 @@ build_apk() {
fi
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
}

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

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

@ -152,7 +152,7 @@ public class CommonSteps extends BaseUserInterfaceTest
public enum Screen
{
LIST_HABITS, SHOW_HABIT, EDIT_HABIT
LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE
}
public static void verifyShowsScreen(Screen screen) {
@ -176,9 +176,15 @@ public class CommonSteps extends BaseUserInterfaceTest
break;
case EDIT_HABIT:
onView(withId(R.id.tvQuestion)).check(matches(isDisplayed()));
onView(withId(R.id.tvDescription)).check(matches(isDisplayed()));
onView(withId(R.id.questionInput)).check(matches(isDisplayed()));
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());
}
public static void pickFrequency(String freq)
public static void pickFrequency()
{
onView(withId(R.id.spinner)).perform(click());
device.findObject(By.text(freq)).click();
onView(withId(R.id.frequencyPicker)).perform(click());
onView(withText("SAVE")).perform(click());
}
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();
}
public static void typeName(String name)
{
typeTextWithId(R.id.tvName, name);
typeTextWithId(R.id.nameInput, name);
}
public static void typeQuestion(String name)
{
typeTextWithId(R.id.tvQuestion, name);
typeTextWithId(R.id.questionInput, name);
}
public static void typeDescription(String description)
{
typeTextWithId(R.id.tvDescription, description);
typeTextWithId(R.id.notesInput, description);
}
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());
}
public static void clickReminderDays()
{
onView(withId(R.id.tvReminderDays)).perform(click());
onView(withId(R.id.reminderDatePicker)).perform(click());
}
public static void unselectAllDays()

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

@ -1,229 +1,215 @@
<?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/>.
-->
<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"/>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.isoron.uhabits">
<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" />
<application
android:name=".HabitsApplication"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title"
android:supportsRtl="true"
android:theme="@style/AppBaseTheme">
android:name=".HabitsApplication"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title"
android:supportsRtl="true"
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
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
<activity
android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true"
android:label="@string/main_activity_title"
android:launchMode="singleTop"/>
android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true"
android:label="@string/main_activity_title"
android:launchMode="singleTop" />
<activity-alias
android:name=".MainActivity"
android:label="@string/main_activity_title"
android:launchMode="singleTop"
android:targetActivity=".activities.habits.list.ListHabitsActivity">
android:name=".MainActivity"
android:label="@string/main_activity_title"
android:launchMode="singleTop"
android:targetActivity=".activities.habits.list.ListHabitsActivity">
<intent-filter android:label="@string/main_activity_title">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name=".activities.habits.show.ShowHabitActivity"
android:label="@string/title_activity_show_habit">
android:name=".activities.habits.show.ShowHabitActivity"
android:label="@string/title_activity_show_habit">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.intro.IntroActivity"
android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
android:name=".activities.intro.IntroActivity"
android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name=".activities.about.AboutActivity"
android:label="@string/about">
android:name=".activities.about.AboutActivity"
android:label="@string/about">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
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">
android:name="android.support.PARENT_ACTIVITY"
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>
<receiver
android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark">
android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info" />
</receiver>
<service android:name=".widgets.StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
<service
android:name=".widgets.StackWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info" />
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info" />
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info" />
</receiver>
<receiver
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info" />
</receiver>
<receiver android:name=".receivers.ReminderReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.WidgetReceiver">
<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
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</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
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</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
android:host="org.isoron.uhabits"
android:scheme="content"/>
android:host="org.isoron.uhabits"
android:scheme="content" />
</intent-filter>
</receiver>
<!-- Locale/Tasker -->
</receiver> <!-- Locale/Tasker -->
<activity
android:name=".automation.EditSettingActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
android:name=".automation.EditSettingActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING"/>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter>
</activity>
<!-- Locale/Tasker -->
</activity> <!-- Locale/Tasker -->
<receiver
android:name=".automation.FireSettingReceiver"
android:exported="true">
android:name=".automation.FireSettingReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.isoron.uhabits"
android:exported="false"
android:grantUriPermissions="true">
android:name="androidx.core.content.FileProvider"
android:authorities="org.isoron.uhabits"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service
android:name=".sync.SyncService"
android:enabled="true"
android:exported="false">
</service>
android:name=".sync.SyncService"
android:enabled="true"
android:exported="false"></service>
</application>
</manifest>

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

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

@ -21,10 +21,17 @@ package org.isoron.uhabits.activities
import dagger.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.ui.*
@Module
abstract class HabitsActivityModule {
@Binds @ActivityScope
internal abstract fun getThemeSwitcher(t: AndroidThemeSwitcher): ThemeSwitcher
class HabitsActivityModule {
@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 hideArchivedItem = menu.findItem(R.id.actionHideArchived)
val hideCompletedItem = menu.findItem(R.id.actionHideCompleted)
val addNumericalHabit = menu.findItem(R.id.actionCreateNumeralHabit)
addNumericalHabit.isVisible = preferences.isDeveloper
nightModeItem.isChecked = themeSwitcher.isNightMode
hideArchivedItem.isChecked = !preferences.showArchived
hideCompletedItem.isChecked = !preferences.showCompleted
@ -54,13 +52,8 @@ class ListHabitsMenu @Inject constructor(
return true
}
R.id.actionCreateBooleanHabit -> {
behavior.onCreateBooleanHabit()
return true
}
R.id.actionCreateNumeralHabit -> {
behavior.onCreateNumericalHabit()
R.id.actionCreateHabit -> {
behavior.onCreateHabit()
return true
}

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

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

@ -21,8 +21,10 @@ package org.isoron.uhabits.intents
import android.content.*
import android.net.*
import org.isoron.androidbase.activities.*
import org.isoron.uhabits.*
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.intro.*
import org.isoron.uhabits.activities.settings.*
@ -81,4 +83,14 @@ class IntentFactory
fun codeContributors(context: Context) =
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.graphics.*;
import android.os.*;
import androidx.annotation.Nullable;
@ -66,7 +67,8 @@ public class SnoozeDelayPickerActivity extends FragmentActivity
},
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
DateFormat.is24HourFormat(this));
DateFormat.is24HourFormat(this),
Color.BLUE);
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">
<item
android:id="@+id/actionCreateBooleanHabit"
android:id="@+id/actionCreateHabit"
android:enabled="true"
android:icon="?iconAdd"
android:title="@string/add_habit"
@ -96,11 +96,4 @@
android:title="@string/about"
app:showAsAction="never"/>
<item
android:id="@+id/actionCreateNumeralHabit"
android:enabled="true"
android:title="Add Numerical Habit"
android:orderInCategory="200"
app:showAsAction="never" />
</menu>

@ -245,6 +245,15 @@
<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="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>

@ -18,10 +18,11 @@
-->
<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="android:dialogTheme">@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="actionModeBackground">@color/blue_grey_700</item>
@ -30,9 +31,9 @@
<item name="selectedBackground">@drawable/selected_box_light</item>
<item name="cardBackground">@drawable/card_light_background</item>
<item name="colorPrimary">@color/blue_grey_800</item>
<item name="colorPrimaryDark">@color/blue_grey_900</item>
<item name="colorAccent">?aboutScreenColor</item>
<item name="colorPrimary">#363636</item>
<item name="colorPrimaryDark">#303030</item>
<item name="colorAccent">#303030</item>
<item name="cardBgColor">@color/grey_50</item>
<item name="windowBackgroundColor">@color/grey_200</item>
<item name="headerBackgroundColor">@color/grey_200</item>
@ -74,7 +75,7 @@
<item name="android:textSize">@dimen/smallTextSize</item>
</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="android:dialogTheme">@style/Theme.AppCompat.Dialog</item>
<item name="android:alertDialogTheme">@style/Theme.AppCompat.Dialog</item>
@ -256,4 +257,86 @@
<style name="ScrollableRecyclerViewStyle" parent="android:Widget">
<item name="android:scrollbars">vertical</item>
</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>

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

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

Loading…
Cancel
Save