diff --git a/.secret/decrypt.sh b/.secret/decrypt.sh index ec2bfbf77..bde9192bb 100755 --- a/.secret/decrypt.sh +++ b/.secret/decrypt.sh @@ -4,13 +4,13 @@ if [ -z "$GPG_PASSWORD" ]; then echo Env variable GPG_PASSWORD must be defined exit 1 fi -for file in gcp-key.json keystore.jks gradle.properties env; do - gpg \ - --quiet \ - --batch \ - --yes \ - --decrypt \ - --passphrase="$GPG_PASSWORD" \ - --output $file \ - $file.gpg -done +gpg \ + --quiet \ + --batch \ + --yes \ + --decrypt \ + --passphrase="$GPG_PASSWORD" \ + --output secret.tar.gz \ + secret +tar -xzf secret.tar.gz +rm secret.tar.gz diff --git a/.secret/env.gpg b/.secret/env.gpg deleted file mode 100644 index d829dcdf2..000000000 Binary files a/.secret/env.gpg and /dev/null differ diff --git a/.secret/gcp-key.json.gpg b/.secret/gcp-key.json.gpg deleted file mode 100644 index f2c9704ba..000000000 Binary files a/.secret/gcp-key.json.gpg and /dev/null differ diff --git a/.secret/gradle.properties.gpg b/.secret/gradle.properties.gpg deleted file mode 100644 index 70d03a5a4..000000000 --- a/.secret/gradle.properties.gpg +++ /dev/null @@ -1 +0,0 @@ -  jcw_UҮ|H5< _Ud5W\#+ w/,:"qDtp6(ez+D|^R9QʋtUwЩk S"g ?d֠=:M0n|Ie/h}u [Dy>܃v`iU4ͻN5Mpΐ_$ \ No newline at end of file diff --git a/.secret/keystore.jks.gpg b/.secret/keystore.jks.gpg deleted file mode 100644 index 72176806d..000000000 Binary files a/.secret/keystore.jks.gpg and /dev/null differ diff --git a/.secret/secret b/.secret/secret new file mode 100644 index 000000000..e3f06089b Binary files /dev/null and b/.secret/secret differ diff --git a/README.md b/README.md index f5592ef89..72836df95 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ source.

Get it on Google Play - Git if on F-Droid + Get it on F-Droid

## Screenshots @@ -21,34 +21,32 @@ source. ## Features -* **Simple, beautiful and modern interface.** Loop has a minimalistic interface - that is easy to use and follows the material design guidelines. +* Beautiful, minimalistic and lightweight interface. +Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones. -* **Habit score.** In addition to showing your current streak, Loop has an - advanced algorithm for calculating the strength of your habits. Every - repetition makes your habit stronger, and every missed day makes it weaker. A - few missed days after a long streak, however, will not completely destroy - your entire progress. +* Habit score. +Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your progress, unlike many other don't-break-the-chain apps. -* **Detailed graphs and statistics.** Clearly see how your habits improved over - time with beautiful and detailed graphs. Scroll back to see the complete - history of your habits. +* Flexible schedules. +In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day. -* **Flexible schedules.** Supports both daily habits and habits with more - complex schedules, such as 3 times every week; one time every other week; or - every other day. +* Reminders. +Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification. -* **Reminders.** Create an individual reminder for each habit, at a chosen hour - of the day. Easily check, dismiss or snooze your habit directly from the - notification, without opening the app. +* Widgets. +Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app. -* **Optimized for smartwatches.** Reminders can be checked, snoozed or - dismissed directly from your Android Wear watch. +* Take control of your data. +If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, checkmarks can be added through other apps, such as Tasker. -* **Completely ad-free and open source.** There are absolutely no - advertisements, annoying notifications or intrusive permissions in this app, - and there will never be. The complete source code is available under the - GPLv3. +* No limitations. +Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users. There are no in-app purchases. + +* Completely ad-free and open source. +There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3). + +* Works offline and respects your privacy. +Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it. ## Installing @@ -85,20 +83,20 @@ contribute, even if you are not a software developer. - Copyright (C) 2016-2019 Álinson Santos Xavier + Copyright (C) 2016-2019 Álinson Santos Xavier - 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 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. + 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 . + You should have received a copy of the GNU General Public License along + with this program. If not, see . [screen1]: screenshots/uhabits1.png [screen2]: screenshots/uhabits2.png diff --git a/android/gradle.properties b/android/gradle.properties index 000388cf6..e2f499871 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ -VERSION_CODE = 45 -VERSION_NAME = 1.8.2 +VERSION_CODE = 46 +VERSION_NAME = 1.8.3 MIN_SDK_VERSION = 21 TARGET_SDK_VERSION = 29 diff --git a/android/play b/android/play new file mode 120000 index 000000000..1ecaa16de --- /dev/null +++ b/android/play @@ -0,0 +1 @@ +uhabits-android/src/main/play/ \ No newline at end of file diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseUserInterfaceTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseUserInterfaceTest.java index a7de248f9..142de3e92 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseUserInterfaceTest.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseUserInterfaceTest.java @@ -115,4 +115,10 @@ public class BaseUserInterfaceTest h4.setColor(2); habitList.update(h4); } + + protected void rotateDevice() throws Exception + { + device.setOrientationLeft(); + device.setOrientationNatural(); + } } diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java index ab28be55b..0a7c2646b 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java @@ -19,7 +19,6 @@ package org.isoron.uhabits.acceptance.steps; -import android.os.*; import android.support.annotation.*; import android.support.test.espresso.*; import android.support.test.espresso.contrib.*; @@ -30,8 +29,7 @@ import org.isoron.uhabits.*; import org.isoron.uhabits.R; import org.isoron.uhabits.activities.habits.list.*; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION.*; import static android.support.test.espresso.Espresso.*; import static android.support.test.espresso.action.ViewActions.*; import static android.support.test.espresso.assertion.PositionAssertions.*; diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java index 467c33959..56440730c 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java @@ -58,6 +58,28 @@ public class EditHabitSteps typeTextWithId(R.id.tvDescription, name); } + public static void setReminder() + { + onView(withId(R.id.tvReminderTime)).perform(click()); + onView(withId(R.id.done_button)).perform(click()); + } + + public static void clickReminderDays() + { + onView(withId(R.id.tvReminderDays)).perform(click()); + } + + public static void unselectAllDays() + { + onView(withText("Saturday")).perform(click()); + onView(withText("Sunday")).perform(click()); + onView(withText("Monday")).perform(click()); + onView(withText("Tuesday")).perform(click()); + onView(withText("Wednesday")).perform(click()); + onView(withText("Thursday")).perform(click()); + onView(withText("Friday")).perform(click()); + } + private static void typeTextWithId(int id, String name) { onView(withId(id)).perform(clearText(), typeText(name), closeSoftKeyboard()); diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt index 87c445d51..8b0416cc1 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.kt @@ -83,11 +83,12 @@ class CheckmarkPanelViewTest : BaseViewTest() { // assertRenders(view, "$PATH/render_reversed.png") // } - @Test - fun testRender_withOffset() { - view.dataOffset = 3 - assertRenders(view, "$PATH/render_offset.png") - } +// // Flaky test +// @Test +// fun testRender_withOffset() { +// view.dataOffset = 3 +// assertRenders(view, "$PATH/render_offset.png") +// } @Test fun testToggle() { diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt index 489f65247..53a9aa5b1 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -77,11 +77,12 @@ class NumberPanelViewTest : BaseViewTest() { assertRenders(view, "$PATH/render_reversed.png") } - @Test - fun testRender_withOffset() { - view.dataOffset = 3 - assertRenders(view, "$PATH/render_offset.png") - } +// // Flaky test +// @Test +// fun testRender_withOffset() { +// view.dataOffset = 3 +// assertRenders(view, "$PATH/render_offset.png") +// } @Test fun testEdit() { diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/integration/SavedStateTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/integration/SavedStateTest.java deleted file mode 100644 index 391e4e37b..000000000 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/integration/SavedStateTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - */ - -package org.isoron.uhabits.integration; - -import android.support.test.filters.*; -import android.support.test.runner.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.about.*; -import org.isoron.uhabits.activities.habits.list.*; -import org.junit.*; -import org.junit.runner.*; - -import static java.lang.Thread.*; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class SavedStateTest extends BaseUserInterfaceTest -{ - /** - * Make sure that the main activity can be recreated by using - * BundleSavedState after being destroyed. See bug: - * https://github.com/iSoron/uhabits/issues/287 - */ - @Test - public void testBundleSavedState() throws Exception - { - startActivity(ListHabitsActivity.class); - device.waitForIdle(); - startActivity(AboutActivity.class); - sleep(1000); - device.pressBack(); - } -} diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/regression/SavedStateTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/regression/SavedStateTest.kt new file mode 100644 index 000000000..1418c56c7 --- /dev/null +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/regression/SavedStateTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 Álinson Santos Xavier + * + * 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 . + */ + +package org.isoron.uhabits.regression + +import android.support.test.filters.* + +import org.isoron.uhabits.* +import org.junit.* + +import org.isoron.uhabits.acceptance.steps.CommonSteps.* +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.* +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.* +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.* +import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText +import org.isoron.uhabits.activities.about.* +import org.isoron.uhabits.activities.habits.list.* +import java.lang.Thread.* + +@LargeTest +class SavedStateTest : BaseUserInterfaceTest() { + + @Test + @Throws(Exception::class) + fun shouldNotCrashWhenRotatingWeekdayPickerDialog() { + // https://github.com/iSoron/uhabits/issues/534 + launchApp() + clickMenu(ADD) + setReminder() + clickReminderDays() + unselectAllDays() + rotateDevice() + clickText("Monday") + } + + /** + * Make sure that the main activity can be recreated by using + * BundleSavedState after being destroyed. See bug: + * https://github.com/iSoron/uhabits/issues/287 + */ + @Test + @Throws(Exception::class) + fun testBundleSavedState() { + launchApp() + startActivity(AboutActivity::class.java) + sleep(1000) + device.pressBack() + } +} diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java index 41b9346c1..753569b79 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java @@ -19,17 +19,19 @@ package org.isoron.uhabits.activities.common.dialogs; -import android.app.*; -import android.content.*; -import android.os.*; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; -import android.support.v7.app.*; +import android.support.v7.app.AppCompatDialogFragment; -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; +import org.isoron.uhabits.R; +import org.isoron.uhabits.core.models.WeekdayList; +import org.isoron.uhabits.core.utils.DateUtils; -import java.util.*; +import java.util.Calendar; /** * Dialog that allows the user to pick one or more days of the week. @@ -38,6 +40,7 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener { + private static final String KEY_SELECTED_DAYS = "selectedDays"; private boolean[] selectedDays; private OnWeekdaysPickedListener listener; @@ -48,6 +51,21 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements selectedDays[which] = isChecked; } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if(savedInstanceState != null){ + selectedDays = savedInstanceState.getBooleanArray(KEY_SELECTED_DAYS); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBooleanArray(KEY_SELECTED_DAYS, selectedDays); + } + @Override public void onClick(DialogInterface dialog, int which) { diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java index 6ff96bb3b..01b7133dd 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java @@ -19,7 +19,7 @@ package org.isoron.uhabits.activities.habits.edit; -import android.app.Dialog; +import android.app.*; import android.content.*; import android.os.*; import android.support.annotation.*; @@ -27,7 +27,7 @@ import android.support.v7.app.*; import android.text.format.*; import android.view.*; -import com.android.datetimepicker.time.*; +import com.android.datetimepicker.time.TimePickerDialog; import org.isoron.uhabits.*; import org.isoron.uhabits.R; @@ -40,7 +40,7 @@ import org.isoron.uhabits.core.preferences.*; import butterknife.*; -import static android.view.View.GONE; +import static android.view.View.*; public class EditHabitDialog extends AppCompatDialogFragment { @@ -48,6 +48,8 @@ public class EditHabitDialog extends AppCompatDialogFragment public static final String BUNDLE_HABIT_TYPE = "habitType"; + private static final String WEEKDAY_PICKER_TAG = "weekdayPicker"; + protected Habit originalHabit; protected Preferences prefs; @@ -109,6 +111,8 @@ public class EditHabitDialog extends AppCompatDialogFragment setupReminderController(); setupNameController(); + restoreChildFragmentListeners(); + return view; } @@ -268,8 +272,16 @@ public class EditHabitDialog extends AppCompatDialogFragment WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(reminderPanel); dialog.setSelectedDays(currentDays); - dialog.show(getFragmentManager(), "weekdayPicker"); + dialog.show(getChildFragmentManager(), WEEKDAY_PICKER_TAG); } }); } + + private void restoreChildFragmentListeners() + { + final WeekdayPickerDialog dialog = + (WeekdayPickerDialog) getChildFragmentManager() + .findFragmentByTag(WEEKDAY_PICKER_TAG); + if(dialog != null) dialog.setListener(reminderPanel); + } } diff --git a/android/uhabits-android/src/main/play/listings/en-US/full-description.txt b/android/uhabits-android/src/main/play/listings/en-US/full-description.txt index e47821d8d..603cc7ca5 100644 --- a/android/uhabits-android/src/main/play/listings/en-US/full-description.txt +++ b/android/uhabits-android/src/main/play/listings/en-US/full-description.txt @@ -1,28 +1,29 @@ -Loop helps you create and maintain good habits, allowing you to achieve your long-term goals. Detailed charts and statistics show you how your habits improved over time. The app is completely ad-free, open source and it respects your privacy. +Loop Habit Tracker helps you create and maintain long-term positive habits in your life. Detailed charts and statistics give you a clear picture of how your habits have improved over time. The app is completely ad-free, open source and it respects your privacy. -Simple, beautiful and modern interface -Loop has a minimalistic interface that is very easy to use and follows the material design guidelines. +Beautiful, minimalistic and lightweight interface +Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones. Habit score -In addition to showing your current streak, Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress, unlike other don't-break-the-chain apps. - -Detailed graphs and statistics -Clearly see how your habits improved over time with detailed charts and statistics. Scroll back to see the complete history of your habits. +Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your progress, unlike many other don't-break-the-chain apps. Flexible schedules -Supports not only daily habits, but also habits with more complex schedules, such as 3 times every week; one time every other week; or every other day. +In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day. Reminders -Create an individual reminder for each habit, at a chosen hour of the day. Easily check, dismiss or snooze your habit directly from the notification, without opening the app. +Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification. Widgets -Track your habits directly from your home screen, with beautiful and colorful widgets. +Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app. + +Take control of your data +If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, checkmarks can be added through other apps, such as Tasker. + +No limitations +Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users. There are no in-app purchases. Completely ad-free and open source -There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under an open-source license (GPLv3). +There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3). Works offline and respects your privacy -Loop doesn't require an Internet connection or online account registration. Your confidential habit data never leaves your phone. Neither the developers nor any third-parties have access to it. +Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it. -Take your data with you -If you want to further analyze your data or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database format (SQLite). diff --git a/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt b/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt index e5eea79b4..51d97da8d 100644 --- a/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt +++ b/android/uhabits-android/src/main/play/release-notes/en-US/alpha.txt @@ -1,6 +1,5 @@ -1.8.2: -* Fix issues with reminders -* Fix crash (HUAWEI) +1.8.3 +* Bugfixes 1.8: * New bar chart showing number of repetitions performed each week, month or year * Performing habits on irregular weekdays will no longer break your streak diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java index af311d85f..deca3de9a 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Timestamp.java @@ -30,7 +30,6 @@ import static org.isoron.uhabits.core.utils.StringUtils.*; public final class Timestamp { - public static final long DAY_LENGTH = 86400000; public static final Timestamp ZERO = new Timestamp(0); @@ -39,10 +38,13 @@ public final class Timestamp public Timestamp(long unixTime) { - if (unixTime < 0 || unixTime % DAY_LENGTH != 0) + if (unixTime < 0) throw new IllegalArgumentException( "Invalid unix time: " + unixTime); + if (unixTime % DAY_LENGTH != 0) + unixTime = (unixTime / DAY_LENGTH) * DAY_LENGTH; + this.unixTime = unixTime; } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/TimestampTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/TimestampTest.java index 55155a8a4..25b5cd897 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/TimestampTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/TimestampTest.java @@ -22,6 +22,7 @@ package org.isoron.uhabits.core.models; import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.utils.*; import org.junit.*; +import org.mockito.internal.verification.*; import static junit.framework.TestCase.assertFalse; import static org.hamcrest.MatcherAssert.*; @@ -64,5 +65,10 @@ public class TimestampTest extends BaseUnitTest assertThat(t.daysUntil(t.minus(300)), equalTo(-300)); } - + @Test + public void testInexact() throws Exception + { + Timestamp t = new Timestamp(1578054764000L); + assertThat(t.getUnixTime(), equalTo(1578009600000L)); + } }