diff --git a/android-pickers/build.gradle b/android-pickers/build.gradle
index c644c66ad..f33f8ca57 100644
--- a/android-pickers/build.gradle
+++ b/android-pickers/build.gradle
@@ -19,6 +19,11 @@ android {
}
}
buildToolsVersion '26.0.2'
+
+ compileOptions {
+ targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
diff --git a/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java b/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java
index 0bbb741e2..06c121b3c 100644
--- a/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java
+++ b/android-pickers/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java
@@ -63,6 +63,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
private static final int PULSE_ANIMATOR_DELAY = 300;
private OnTimeSetListener mCallback;
+ private DialogInterface.OnDismissListener dismissListener;
private HapticFeedbackController mHapticFeedbackController;
@@ -116,7 +117,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
*/
void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute);
- void onTimeCleared(RadialPickerLayout view);
+ default void onTimeCleared(RadialPickerLayout view) {}
}
public TimePickerDialog() {
@@ -998,4 +999,15 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
return false;
}
}
+
+ public void setDismissListener( DialogInterface.OnDismissListener listener ) {
+ dismissListener = listener;
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if( dismissListener != null )
+ dismissListener.onDismiss(dialog);
+ }
}
diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml
index ec52f93f1..2f371d686 100644
--- a/uhabits-android/src/main/AndroidManifest.xml
+++ b/uhabits-android/src/main/AndroidManifest.xml
@@ -91,6 +91,11 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
+
+
+ private val listController: Lazy,
+ private val notificationTray: NotificationTray
) : BaseSelectionMenu() {
override fun onFinish() {
@@ -69,6 +74,12 @@ class ListHabitsSelectionMenu @Inject constructor(
return true
}
+ R.id.action_notify -> {
+ for(h in listAdapter.selected)
+ notificationTray.show(h, DateUtils.getToday(), 0)
+ return true
+ }
+
else -> return false
}
}
@@ -78,12 +89,14 @@ class ListHabitsSelectionMenu @Inject constructor(
val itemColor = menu.findItem(R.id.action_color)
val itemArchive = menu.findItem(R.id.action_archive_habit)
val itemUnarchive = menu.findItem(R.id.action_unarchive_habit)
+ val itemNotify = menu.findItem(R.id.action_notify)
itemColor.isVisible = true
itemEdit.isVisible = behavior.canEdit()
itemArchive.isVisible = behavior.canArchive()
itemUnarchive.isVisible = behavior.canUnarchive()
setTitle(Integer.toString(listAdapter.selected.size))
+ itemNotify.isVisible = prefs.isDeveloper
return true
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java
index eeb11c6aa..c74b412da 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java
@@ -26,6 +26,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.dialogs.*;
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 javax.inject.*;
@@ -45,6 +46,9 @@ public class ShowHabitScreen extends BaseScreen
@NonNull
private final EditHabitDialogFactory editHabitDialogFactory;
+ @NonNull
+ private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
+
private final Lazy behavior;
@Inject
@@ -52,8 +56,8 @@ public class ShowHabitScreen extends BaseScreen
@NonNull Habit habit,
@NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu,
- @NonNull
- EditHabitDialogFactory editHabitDialogFactory,
+ @NonNull EditHabitDialogFactory editHabitDialogFactory,
+ @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull Lazy behavior)
{
super(activity);
@@ -63,6 +67,7 @@ public class ShowHabitScreen extends BaseScreen
this.habit = habit;
this.behavior = behavior;
this.editHabitDialogFactory = editHabitDialogFactory;
+ this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
view.setController(this);
}
@@ -116,6 +121,19 @@ public class ShowHabitScreen extends BaseScreen
{
case COULD_NOT_EXPORT:
showMessage(R.string.could_not_export);
+
+ case HABIT_DELETED:
+ showMessage(R.string.delete_habits_message);
}
}
+
+ @Override
+ public void showDeleteConfirmationScreen(@NonNull OnConfirmedCallback callback) {
+ activity.showDialog(confirmDeleteDialogFactory.create(callback));
+ }
+
+ @Override
+ public void close() {
+ activity.finish();
+ }
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitsMenu.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitsMenu.java
index fe30a3e31..733db302c 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitsMenu.java
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitsMenu.java
@@ -57,6 +57,10 @@ public class ShowHabitsMenu extends BaseMenu
behavior.get().onExportCSV();
return true;
+ case R.id.action_delete:
+ behavior.get().onDeleteHabit();
+ return true;
+
default:
return false;
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
index 2758ba934..b60904840 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
@@ -54,6 +54,15 @@ class PendingIntentFactory
},
FLAG_UPDATE_CURRENT)
+ fun removeRepetition(habit: Habit): PendingIntent =
+ PendingIntent.getBroadcast(
+ context, 3,
+ Intent(context, WidgetReceiver::class.java).apply {
+ action = WidgetReceiver.ACTION_REMOVE_REPETITION
+ data = Uri.parse(habit.uriString)
+ },
+ FLAG_UPDATE_CURRENT)
+
fun showHabit(habit: Habit): PendingIntent =
android.support.v4.app.TaskStackBuilder
.create(context)
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt
index 6a25300d0..f41c18bca 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt
@@ -23,6 +23,7 @@ import android.app.*
import android.content.*
import android.graphics.*
import android.graphics.BitmapFactory.*
+import android.support.annotation.*
import android.support.v4.app.*
import android.support.v4.app.NotificationCompat.*
import org.isoron.androidbase.*
@@ -43,18 +44,39 @@ class AndroidNotificationTray
private val ringtoneManager: RingtoneManager
) : NotificationTray.SystemTray {
+ private var active = HashSet()
+
override fun removeNotification(id: Int) {
- NotificationManagerCompat.from(context).cancel(id)
+ val manager = NotificationManagerCompat.from(context)
+ manager.cancel(id)
+ active.remove(id)
+
+ // Clear the group summary notification
+ if(active.isEmpty()) manager.cancelAll()
}
override fun showNotification(habit: Habit,
notificationId: Int,
timestamp: Timestamp,
- reminderTime: Long) {
+ reminderTime: Long)
+ {
+ val notificationManager = NotificationManagerCompat.from(context)
+ val summary = buildSummary(reminderTime)
+ notificationManager.notify(Int.MAX_VALUE, summary)
+ val notification = buildNotification(habit, reminderTime, timestamp)
+ notificationManager.notify(notificationId, notification)
+ active.add(notificationId)
+ }
- val checkAction = Action(
+ @NonNull
+ fun buildNotification(@NonNull habit: Habit,
+ @NonNull reminderTime: Long,
+ @NonNull timestamp: Timestamp) : Notification
+ {
+
+ val addRepetitionAction = Action(
R.drawable.ic_action_check,
- context.getString(R.string.check),
+ context.getString(R.string.yes),
pendingIntents.addCheckmark(habit, timestamp))
val snoozeAction = Action(
@@ -62,6 +84,11 @@ class AndroidNotificationTray
context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit))
+ val removeRepetitionAction = Action(
+ R.drawable.ic_action_cancel,
+ context.getString(R.string.no),
+ pendingIntents.removeRepetition(habit))
+
val wearableBg = decodeResource(context.resources, R.drawable.stripe)
// Even though the set of actions is the same on the phone and
@@ -69,7 +96,8 @@ class AndroidNotificationTray
// WearableExtender.
val wearableExtender = WearableExtender()
.setBackground(wearableBg)
- .addAction(checkAction)
+ .addAction(addRepetitionAction)
+ .addAction(removeRepetitionAction)
.addAction(snoozeAction)
val builder = NotificationCompat.Builder(context)
@@ -78,20 +106,32 @@ class AndroidNotificationTray
.setContentText(habit.description)
.setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit))
- .addAction(checkAction)
+ .addAction(addRepetitionAction)
+ .addAction(removeRepetitionAction)
.addAction(snoozeAction)
.setSound(ringtoneManager.getURI())
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.setOngoing(preferences.shouldMakeNotificationsSticky())
+ .setGroup("default")
if (preferences.shouldMakeNotificationsLed())
builder.setLights(Color.RED, 1000, 1000)
- val notificationManager = context.getSystemService(
- Activity.NOTIFICATION_SERVICE) as NotificationManager
+ return builder.build()
+ }
- notificationManager.notify(notificationId, builder.build())
+ @NonNull
+ private fun buildSummary(@NonNull reminderTime: Long) : Notification
+ {
+ return NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle(context.getString(R.string.app_name))
+ .setWhen(reminderTime)
+ .setShowWhen(true)
+ .setGroup("default")
+ .setGroupSummary(true)
+ .build()
}
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java
new file mode 100644
index 000000000..03d26a707
--- /dev/null
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/SnoozeDelayPickerActivity.java
@@ -0,0 +1,85 @@
+package org.isoron.uhabits.notifications;
+
+
+import android.app.*;
+import android.os.*;
+import android.support.annotation.*;
+import android.support.v4.app.*;
+import android.text.format.*;
+import android.view.*;
+import android.widget.*;
+
+import com.android.datetimepicker.time.TimePickerDialog;
+
+import org.isoron.uhabits.*;
+import org.isoron.uhabits.core.models.*;
+import org.isoron.uhabits.receivers.*;
+
+import java.util.*;
+
+import static android.content.ContentUris.parseId;
+
+public class SnoozeDelayPickerActivity extends FragmentActivity
+ implements AdapterView.OnItemClickListener
+{
+ private Habit habit;
+
+ private ReminderController reminderController;
+
+ @Override
+ protected void onCreate(@Nullable Bundle bundle)
+ {
+ super.onCreate(bundle);
+ if (getIntent() == null) finish();
+ if (getIntent().getData() == null) finish();
+
+ HabitsApplication app = (HabitsApplication) getApplicationContext();
+ HabitsApplicationComponent appComponent = app.getComponent();
+ reminderController = appComponent.getReminderController();
+ habit = appComponent.getHabitList().getById(parseId(getIntent().getData()));
+ if (habit == null) finish();
+
+ int theme = R.style.Theme_AppCompat_Light_Dialog_Alert;
+ AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(this, theme))
+ .setTitle(R.string.select_snooze_delay)
+ .setItems(R.array.snooze_picker_names, null)
+ .create();
+
+ dialog.getListView().setOnItemClickListener(this);
+ dialog.setOnDismissListener(d -> finish());
+ dialog.show();
+ }
+
+ private void showTimePicker()
+ {
+ final Calendar calendar = Calendar.getInstance();
+ TimePickerDialog dialog = TimePickerDialog.newInstance(
+ (view, hour, minute) -> {
+ reminderController.onSnoozeTimePicked(habit, hour, minute);
+ finish();
+ },
+ calendar.get(Calendar.HOUR_OF_DAY),
+ calendar.get(Calendar.MINUTE),
+ DateFormat.is24HourFormat(this));
+ dialog.show(getSupportFragmentManager(), "timePicker");
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id)
+ {
+ int[] snoozeValues = getResources().getIntArray(R.array.snooze_picker_values);
+ if (snoozeValues[position] >= 0)
+ {
+ reminderController.onSnoozeDelayPicked(habit, snoozeValues[position]);
+ finish();
+ }
+ else showTimePicker();
+ }
+
+ @Override
+ public void finish()
+ {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java
index ae88f55c9..c253064f1 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderController.java
@@ -19,19 +19,21 @@
package org.isoron.uhabits.receivers;
+import android.content.*;
+import android.net.*;
import android.support.annotation.*;
+import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
import org.isoron.uhabits.core.ui.*;
+import org.isoron.uhabits.core.utils.*;
+import org.isoron.uhabits.notifications.*;
import javax.inject.*;
-import static org.isoron.uhabits.core.utils.DateUtils.applyTimezone;
-import static org.isoron.uhabits.core.utils.DateUtils.getLocalTime;
-
-@ReceiverScope
+@AppScope
public class ReminderController
{
@NonNull
@@ -40,6 +42,7 @@ public class ReminderController
@NonNull
private final NotificationTray notificationTray;
+ @NonNull
private Preferences preferences;
@Inject
@@ -65,14 +68,25 @@ public class ReminderController
reminderScheduler.scheduleAll();
}
- public void onSnooze(@NonNull Habit habit)
+ public void onSnoozePressed(@NonNull Habit habit, final Context context)
{
- long snoozeInterval = preferences.getSnoozeInterval();
+ long delay = preferences.getSnoozeInterval();
- long now = applyTimezone(getLocalTime());
- long reminderTime = now + snoozeInterval * 60 * 1000;
+ if (delay < 0)
+ showSnoozeDelayPicker(habit, context);
+ else
+ scheduleReminderMinutesFromNow(habit, delay);
+ }
- reminderScheduler.schedule(habit, reminderTime);
+ public void onSnoozeDelayPicked(Habit habit, int delay)
+ {
+ scheduleReminderMinutesFromNow(habit, delay);
+ }
+
+ public void onSnoozeTimePicked(Habit habit, int hour, int minute)
+ {
+ Long time = DateUtils.getUpcomingTimeInMillis(hour, minute);
+ reminderScheduler.scheduleAtTime(habit, time);
notificationTray.cancel(habit);
}
@@ -80,4 +94,19 @@ public class ReminderController
{
notificationTray.cancel(habit);
}
+
+ private void scheduleReminderMinutesFromNow(Habit habit, long minutes)
+ {
+ reminderScheduler.scheduleMinutesFromNow(habit, minutes);
+ notificationTray.cancel(habit);
+ }
+
+ private void showSnoozeDelayPicker(@NonNull Habit habit, Context context)
+ {
+ context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ Intent intent = new Intent(context, SnoozeDelayPickerActivity.class);
+ intent.setData(Uri.parse(habit.getUriString()));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java
index cb382a950..dff612345 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/receivers/ReminderReceiver.java
@@ -20,14 +20,13 @@
package org.isoron.uhabits.receivers;
import android.content.*;
+import android.support.annotation.*;
import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
-import dagger.*;
-
import static android.content.ContentUris.*;
/**
@@ -38,30 +37,26 @@ import static android.content.ContentUris.*;
public class ReminderReceiver extends BroadcastReceiver
{
public static final String ACTION_DISMISS_REMINDER =
- "org.isoron.uhabits.ACTION_DISMISS_REMINDER";
+ "org.isoron.uhabits.ACTION_DISMISS_REMINDER";
public static final String ACTION_SHOW_REMINDER =
- "org.isoron.uhabits.ACTION_SHOW_REMINDER";
+ "org.isoron.uhabits.ACTION_SHOW_REMINDER";
public static final String ACTION_SNOOZE_REMINDER =
- "org.isoron.uhabits.ACTION_SNOOZE_REMINDER";
+ "org.isoron.uhabits.ACTION_SNOOZE_REMINDER";
private static final String TAG = "ReminderReceiver";
@Override
- public void onReceive(final Context context, Intent intent)
+ public void onReceive(@Nullable final Context context, @Nullable Intent intent)
{
- HabitsApplication app =
- (HabitsApplication) context.getApplicationContext();
-
- ReminderComponent component = DaggerReminderReceiver_ReminderComponent
- .builder()
- .habitsApplicationComponent(app.getComponent())
- .build();
+ if (context == null || intent == null) return;
+ if (intent.getAction() == null) return;
- HabitList habits = app.getComponent().getHabitList();
- ReminderController reminderController =
- component.getReminderController();
+ HabitsApplication app = (HabitsApplication) context.getApplicationContext();
+ HabitsApplicationComponent appComponent = app.getComponent();
+ HabitList habits = appComponent.getHabitList();
+ ReminderController reminderController = appComponent.getReminderController();
Log.i(TAG, String.format("Received intent: %s", intent.toString()));
@@ -80,7 +75,7 @@ public class ReminderReceiver extends BroadcastReceiver
case ACTION_SHOW_REMINDER:
if (habit == null) return;
reminderController.onShowReminder(habit,
- new Timestamp(timestamp), reminderTime);
+ new Timestamp(timestamp), reminderTime);
break;
case ACTION_DISMISS_REMINDER:
@@ -90,7 +85,7 @@ public class ReminderReceiver extends BroadcastReceiver
case ACTION_SNOOZE_REMINDER:
if (habit == null) return;
- reminderController.onSnooze(habit);
+ reminderController.onSnoozePressed(habit, context);
break;
case Intent.ACTION_BOOT_COMPLETED:
@@ -103,11 +98,4 @@ public class ReminderReceiver extends BroadcastReceiver
Log.e(TAG, "could not process intent", e);
}
}
-
- @ReceiverScope
- @Component(dependencies = HabitsApplicationComponent.class)
- interface ReminderComponent
- {
- ReminderController getReminderController();
- }
}
diff --git a/uhabits-android/src/main/res/drawable-hdpi/ic_action_cancel.png b/uhabits-android/src/main/res/drawable-hdpi/ic_action_cancel.png
new file mode 100644
index 000000000..374fc6fc0
Binary files /dev/null and b/uhabits-android/src/main/res/drawable-hdpi/ic_action_cancel.png differ
diff --git a/uhabits-android/src/main/res/drawable-mdpi/ic_action_cancel.png b/uhabits-android/src/main/res/drawable-mdpi/ic_action_cancel.png
new file mode 100644
index 000000000..e7de03a06
Binary files /dev/null and b/uhabits-android/src/main/res/drawable-mdpi/ic_action_cancel.png differ
diff --git a/uhabits-android/src/main/res/drawable-xhdpi/ic_action_cancel.png b/uhabits-android/src/main/res/drawable-xhdpi/ic_action_cancel.png
new file mode 100644
index 000000000..dbd10906c
Binary files /dev/null and b/uhabits-android/src/main/res/drawable-xhdpi/ic_action_cancel.png differ
diff --git a/uhabits-android/src/main/res/drawable-xxhdpi/ic_action_cancel.png b/uhabits-android/src/main/res/drawable-xxhdpi/ic_action_cancel.png
new file mode 100644
index 000000000..20591def4
Binary files /dev/null and b/uhabits-android/src/main/res/drawable-xxhdpi/ic_action_cancel.png differ
diff --git a/uhabits-android/src/main/res/menu/list_habits_selection.xml b/uhabits-android/src/main/res/menu/list_habits_selection.xml
index 4f274e39e..0e5cd850a 100644
--- a/uhabits-android/src/main/res/menu/list_habits_selection.xml
+++ b/uhabits-android/src/main/res/menu/list_habits_selection.xml
@@ -46,4 +46,9 @@
android:title="@string/delete"
app:showAsAction="never"/>
+
+
\ No newline at end of file
diff --git a/uhabits-android/src/main/res/menu/show_habit.xml b/uhabits-android/src/main/res/menu/show_habit.xml
index 366a53e6c..e14ce108c 100644
--- a/uhabits-android/src/main/res/menu/show_habit.xml
+++ b/uhabits-android/src/main/res/menu/show_habit.xml
@@ -26,6 +26,11 @@
android:title="@string/export"
app:showAsAction="never"/>
+
+
- @string/interval_4_hour
- @string/interval_8_hour
- @string/interval_24_hour
+ - @string/interval_always_ask
@@ -45,8 +46,31 @@
- 240
- 480
- 1440
+ - -1
+
+ - @string/interval_15_minutes
+ - @string/interval_30_minutes
+ - @string/interval_1_hour
+ - @string/interval_2_hour
+ - @string/interval_4_hour
+ - @string/interval_8_hour
+ - @string/interval_24_hour
+ - @string/interval_custom
+
+
+
+ - 15
+ - 30
+ - 60
+ - 120
+ - 240
+ - 480
+ - 1440
+ - -1
+
+
- @string/every_day
- @string/every_week
diff --git a/uhabits-android/src/main/res/values/strings.xml b/uhabits-android/src/main/res/values/strings.xml
index df4cc33c5..55dc0b958 100644
--- a/uhabits-android/src/main/res/values/strings.xml
+++ b/uhabits-android/src/main/res/values/strings.xml
@@ -83,6 +83,8 @@
4 hours
8 hours
24 hours
+ Always ask
+ Custom...
Toggle with short press
Put checkmarks with a single tap instead of press-and-hold. More convenient, but might cause accidental toggles.
Snooze interval on reminders
@@ -95,6 +97,7 @@
Name
Settings
Snooze interval
+ Select snooze delay
Did you know?
To rearrange the entries, press-and-hold on the name of the habit, then drag it to the correct place.
@@ -225,4 +228,6 @@
e.g. Did you exercise today?
Question
Target
+ Yes
+ No
\ No newline at end of file
diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java
index 93b199a10..b17bbace5 100644
--- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java
+++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java
@@ -70,9 +70,9 @@ public class ReminderControllerTest extends BaseAndroidJVMTest
DateUtils.setFixedLocalTime(now);
when(preferences.getSnoozeInterval()).thenReturn(15L);
- controller.onSnooze(habit);
+ controller.onSnoozePressed(habit,null);
- verify(reminderScheduler).schedule(habit, nowTz + 900000);
+ verify(reminderScheduler).scheduleMinutesFromNow(habit, 15L);
verify(notificationTray).cancel(habit);
}
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Reminder.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Reminder.java
index 870954605..8cb0d31f0 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Reminder.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Reminder.java
@@ -22,8 +22,9 @@ package org.isoron.uhabits.core.models;
import android.support.annotation.*;
import org.apache.commons.lang3.builder.*;
+import org.isoron.uhabits.core.utils.*;
-import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
+import static org.isoron.uhabits.core.utils.StringUtils.*;
public final class Reminder
{
@@ -56,6 +57,11 @@ public final class Reminder
return minute;
}
+ public long getTimeInMillis()
+ {
+ return DateUtils.getUpcomingTimeInMillis(hour, minute);
+ }
+
@Override
public boolean equals(Object o)
{
@@ -66,29 +72,29 @@ public final class Reminder
Reminder reminder = (Reminder) o;
return new EqualsBuilder()
- .append(hour, reminder.hour)
- .append(minute, reminder.minute)
- .append(days, reminder.days)
- .isEquals();
+ .append(hour, reminder.hour)
+ .append(minute, reminder.minute)
+ .append(days, reminder.days)
+ .isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
- .append(hour)
- .append(minute)
- .append(days)
- .toHashCode();
+ .append(hour)
+ .append(minute)
+ .append(days)
+ .toHashCode();
}
@Override
public String toString()
{
return new ToStringBuilder(this, defaultToStringStyle())
- .append("hour", hour)
- .append("minute", minute)
- .append("days", days)
- .toString();
+ .append("hour", hour)
+ .append("minute", minute)
+ .append("days", days)
+ .toString();
}
}
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java
index c1d05f517..fe54af3ec 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java
@@ -164,7 +164,7 @@ public class MemoryHabitList extends HabitList
@Override
public synchronized Iterator iterator()
{
- return Collections.unmodifiableCollection(list).iterator();
+ return new ArrayList<>(list).iterator();
}
@Override
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java
index e31ed71ed..51423aac7 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/reminders/ReminderScheduler.java
@@ -24,9 +24,6 @@ import android.support.annotation.*;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
-import org.isoron.uhabits.core.utils.*;
-
-import java.util.*;
import javax.inject.*;
@@ -55,19 +52,24 @@ public class ReminderScheduler implements CommandRunner.Listener
public void onCommandExecuted(@NonNull Command command,
@Nullable Long refreshKey)
{
- if(command instanceof ToggleRepetitionCommand) return;
- if(command instanceof ChangeHabitColorCommand) return;
+ if (command instanceof ToggleRepetitionCommand) return;
+ if (command instanceof ChangeHabitColorCommand) return;
scheduleAll();
}
- public void schedule(@NonNull Habit habit, @Nullable Long reminderTime)
+ public void schedule(@NonNull Habit habit)
+ {
+ if (!habit.hasReminder()) return;
+ Long reminderTime = habit.getReminder().getTimeInMillis();
+ scheduleAtTime(habit, reminderTime);
+ }
+
+ public void scheduleAtTime(@NonNull Habit habit, @NonNull Long reminderTime)
{
+ if (reminderTime == null) throw new IllegalArgumentException();
if (!habit.hasReminder()) return;
if (habit.isArchived()) return;
- Reminder reminder = habit.getReminder();
- if (reminderTime == null) reminderTime = getReminderTime(reminder);
long timestamp = getStartOfDay(removeTimezone(reminderTime));
-
sys.scheduleShowReminder(reminderTime, habit, timestamp);
}
@@ -76,7 +78,7 @@ public class ReminderScheduler implements CommandRunner.Listener
HabitList reminderHabits =
habitList.getFiltered(HabitMatcher.WITH_ALARM);
for (Habit habit : reminderHabits)
- schedule(habit, null);
+ schedule(habit);
}
public void startListening()
@@ -89,19 +91,11 @@ public class ReminderScheduler implements CommandRunner.Listener
commandRunner.removeListener(this);
}
- @NonNull
- private Long getReminderTime(@NonNull Reminder reminder)
+ public void scheduleMinutesFromNow(Habit habit, long minutes)
{
- Calendar calendar = DateUtils.getStartOfTodayCalendar();
- calendar.set(Calendar.HOUR_OF_DAY, reminder.getHour());
- calendar.set(Calendar.MINUTE, reminder.getMinute());
- calendar.set(Calendar.SECOND, 0);
- Long time = calendar.getTimeInMillis();
-
- if (DateUtils.getLocalTime() > time)
- time += DateUtils.DAY_LENGTH;
-
- return applyTimezone(time);
+ long now = applyTimezone(getLocalTime());
+ long reminderTime = now + minutes * 60 * 1000;
+ scheduleAtTime(habit, reminderTime);
}
public interface SystemScheduler
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java
index 2c015b4b6..ad426f528 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java
@@ -21,8 +21,10 @@ package org.isoron.uhabits.core.ui.screens.habits.show;
import android.support.annotation.*;
+import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.tasks.*;
+import org.isoron.uhabits.core.ui.callbacks.*;
import java.io.*;
import java.util.*;
@@ -45,18 +47,23 @@ public class ShowHabitMenuBehavior
@NonNull
private System system;
+ @NonNull
+ private CommandRunner commandRunner;
+
@Inject
public ShowHabitMenuBehavior(@NonNull HabitList habitList,
@NonNull Habit habit,
@NonNull TaskRunner taskRunner,
@NonNull Screen screen,
- @NonNull System system)
+ @NonNull System system,
+ @NonNull CommandRunner commandRunner)
{
this.habitList = habitList;
this.habit = habit;
this.taskRunner = taskRunner;
this.screen = screen;
this.system = system;
+ this.commandRunner = commandRunner;
}
public void onEditHabit()
@@ -77,9 +84,20 @@ public class ShowHabitMenuBehavior
}));
}
+ public void onDeleteHabit()
+ {
+ List selected = Collections.singletonList(habit);
+
+ screen.showDeleteConfirmationScreen(() -> {
+ commandRunner.execute(new DeleteHabitsCommand(habitList, selected),
+ null);
+ screen.close();
+ });
+ }
+
public enum Message
{
- COULD_NOT_EXPORT
+ COULD_NOT_EXPORT, HABIT_DELETED
}
public interface Screen
@@ -89,6 +107,11 @@ public class ShowHabitMenuBehavior
void showMessage(Message m);
void showSendFileScreen(String filename);
+
+ void showDeleteConfirmationScreen(
+ @NonNull OnConfirmedCallback callback);
+
+ void close();
}
public interface System
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java
index 1f9500123..d3aa800af 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java
@@ -48,14 +48,15 @@ public class WidgetBehavior
public void onAddRepetition(@NonNull Habit habit, Timestamp timestamp)
{
+ notificationTray.cancel(habit);
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep != null) return;
performToggle(habit, timestamp);
- notificationTray.cancel(habit);
}
public void onRemoveRepetition(@NonNull Habit habit, Timestamp timestamp)
{
+ notificationTray.cancel(habit);
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep == null) return;
performToggle(habit, timestamp);
diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java
index ff52f47db..149af5969 100644
--- a/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java
+++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java
@@ -240,6 +240,20 @@ public abstract class DateUtils
}
}
+ public static long getUpcomingTimeInMillis(int hour, int minute)
+ {
+ Calendar calendar = DateUtils.getStartOfTodayCalendar();
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+ calendar.set(Calendar.SECOND, 0);
+ Long time = calendar.getTimeInMillis();
+
+ if (DateUtils.getLocalTime() > time)
+ time += DateUtils.DAY_LENGTH;
+
+ return applyTimezone(time);
+ }
+
public enum TruncateField
{
MONTH, WEEK_NUMBER, YEAR, QUARTER
diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java
index 480471670..82e147e16 100644
--- a/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java
+++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java
@@ -118,7 +118,7 @@ public class ReminderSchedulerTest extends BaseUnitTest
@Test
public void testSchedule_withoutReminder()
{
- reminderScheduler.schedule(habit, null);
+ reminderScheduler.schedule(habit);
Mockito.verifyZeroInteractions(sys);
}
@@ -133,7 +133,8 @@ public class ReminderSchedulerTest extends BaseUnitTest
long expectedCheckmarkTime,
long expectedReminderTime)
{
- reminderScheduler.schedule(habit, atTime);
+ if(atTime == null) reminderScheduler.schedule(habit);
+ else reminderScheduler.scheduleAtTime(habit, atTime);
verify(sys).scheduleShowReminder(expectedReminderTime, habit,
expectedCheckmarkTime);
}
diff --git a/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehaviorTest.java b/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehaviorTest.java
index 08aa1a7ea..fefade28e 100644
--- a/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehaviorTest.java
+++ b/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehaviorTest.java
@@ -50,7 +50,7 @@ public class ShowHabitMenuBehaviorTest extends BaseUnitTest
habit = fixtures.createShortHabit();
menu = new ShowHabitMenuBehavior(habitList, habit, taskRunner, screen,
- system);
+ system, commandRunner);
}
@Test