Merge branch 'dev'

pull/542/head
Alinson S. Xavier 6 years ago
commit 2e9330b5c5

@ -4,13 +4,13 @@ if [ -z "$GPG_PASSWORD" ]; then
echo Env variable GPG_PASSWORD must be defined echo Env variable GPG_PASSWORD must be defined
exit 1 exit 1
fi fi
for file in gcp-key.json keystore.jks gradle.properties env; do gpg \
gpg \
--quiet \ --quiet \
--batch \ --batch \
--yes \ --yes \
--decrypt \ --decrypt \
--passphrase="$GPG_PASSWORD" \ --passphrase="$GPG_PASSWORD" \
--output $file \ --output secret.tar.gz \
$file.gpg secret
done tar -xzf secret.tar.gz
rm secret.tar.gz

Binary file not shown.

Binary file not shown.

@ -1 +0,0 @@
Ś  j”cw_U‰čŇ®|H™ń5¨<¤ _UĆdČ5W\#‡ąŚ„Żô+ w/Üń¨,ČĆ:"qűDÂÖt˝pÍŕ†6(±ezŞ+D|˙Ý^RŹ9QĘt‰“UěwĐ©ÚS"g¤ ?d……<E280A6>Ö =:M»áľ0nö¸|żIeŤ»/Ŕ°–h}Řu ö [Dyšă>Ü<>ęěv„`iŃU4§ĐŮŕ­Í»NŞ5MÍpÎ<70><C38E>î_$

Binary file not shown.

Binary file not shown.

@ -7,7 +7,7 @@ source.
<p align="center"> <p align="center">
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a> <a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a> <a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p> </p>
## Screenshots ## Screenshots
@ -21,34 +21,32 @@ source.
## Features ## Features
* **Simple, beautiful and modern interface.** Loop has a minimalistic interface * <b>Beautiful, minimalistic and lightweight interface.</b>
that is easy to use and follows the material design guidelines. 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 * <b>Habit score.</b>
advanced algorithm for calculating the strength of your habits. Every 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.
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.
* **Detailed graphs and statistics.** Clearly see how your habits improved over * <b>Flexible schedules.</b>
time with beautiful and detailed graphs. Scroll back to see the complete In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
history of your habits.
* **Flexible schedules.** Supports both daily habits and habits with more * <b>Reminders.</b>
complex schedules, such as 3 times every week; one time every other week; or 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.
every other day.
* **Reminders.** Create an individual reminder for each habit, at a chosen hour * <b>Widgets.</b>
of the day. Easily check, dismiss or snooze your habit directly from the 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.
notification, without opening the app.
* **Optimized for smartwatches.** Reminders can be checked, snoozed or * <b>Take control of your data.</b>
dismissed directly from your Android Wear watch. 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 * <b>No limitations.</b>
advertisements, annoying notifications or intrusive permissions in this app, 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.
and there will never be. The complete source code is available under the
GPLv3. * <b>Completely ad-free and open source.</b>
There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3).
* <b>Works offline and respects your privacy.</b>
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 ## Installing

@ -1,5 +1,5 @@
VERSION_CODE = 45 VERSION_CODE = 46
VERSION_NAME = 1.8.2 VERSION_NAME = 1.8.3
MIN_SDK_VERSION = 21 MIN_SDK_VERSION = 21
TARGET_SDK_VERSION = 29 TARGET_SDK_VERSION = 29

@ -0,0 +1 @@
uhabits-android/src/main/play/

@ -115,4 +115,10 @@ public class BaseUserInterfaceTest
h4.setColor(2); h4.setColor(2);
habitList.update(h4); habitList.update(h4);
} }
protected void rotateDevice() throws Exception
{
device.setOrientationLeft();
device.setOrientationNatural();
}
} }

@ -19,7 +19,6 @@
package org.isoron.uhabits.acceptance.steps; package org.isoron.uhabits.acceptance.steps;
import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.test.espresso.*; import android.support.test.espresso.*;
import android.support.test.espresso.contrib.*; import android.support.test.espresso.contrib.*;
@ -30,8 +29,7 @@ import org.isoron.uhabits.*;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import org.isoron.uhabits.activities.habits.list.*; import org.isoron.uhabits.activities.habits.list.*;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.*;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.support.test.espresso.Espresso.*; import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.action.ViewActions.*; import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.assertion.PositionAssertions.*; import static android.support.test.espresso.assertion.PositionAssertions.*;

@ -58,6 +58,28 @@ public class EditHabitSteps
typeTextWithId(R.id.tvDescription, name); 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) private static void typeTextWithId(int id, String name)
{ {
onView(withId(id)).perform(clearText(), typeText(name), closeSoftKeyboard()); onView(withId(id)).perform(clearText(), typeText(name), closeSoftKeyboard());

@ -83,11 +83,12 @@ class CheckmarkPanelViewTest : BaseViewTest() {
// assertRenders(view, "$PATH/render_reversed.png") // assertRenders(view, "$PATH/render_reversed.png")
// } // }
@Test // // Flaky test
fun testRender_withOffset() { // @Test
view.dataOffset = 3 // fun testRender_withOffset() {
assertRenders(view, "$PATH/render_offset.png") // view.dataOffset = 3
} // assertRenders(view, "$PATH/render_offset.png")
// }
@Test @Test
fun testToggle() { fun testToggle() {

@ -77,11 +77,12 @@ class NumberPanelViewTest : BaseViewTest() {
assertRenders(view, "$PATH/render_reversed.png") assertRenders(view, "$PATH/render_reversed.png")
} }
@Test // // Flaky test
fun testRender_withOffset() { // @Test
view.dataOffset = 3 // fun testRender_withOffset() {
assertRenders(view, "$PATH/render_offset.png") // view.dataOffset = 3
} // assertRenders(view, "$PATH/render_offset.png")
// }
@Test @Test
fun testEdit() { fun testEdit() {

@ -1,51 +0,0 @@
/*
* Copyright (C) 2017 Á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.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();
}
}

@ -0,0 +1,65 @@
/*
* Copyright (C) 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.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()
}
}

@ -19,17 +19,19 @@
package org.isoron.uhabits.activities.common.dialogs; package org.isoron.uhabits.activities.common.dialogs;
import android.app.*; import android.app.Dialog;
import android.content.*; import android.content.DialogInterface;
import android.os.*; 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.AlertDialog;
import android.support.v7.app.*; import android.support.v7.app.AppCompatDialogFragment;
import org.isoron.uhabits.*; import org.isoron.uhabits.R;
import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.models.WeekdayList;
import org.isoron.uhabits.core.utils.*; 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. * 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.OnMultiChoiceClickListener,
DialogInterface.OnClickListener DialogInterface.OnClickListener
{ {
private static final String KEY_SELECTED_DAYS = "selectedDays";
private boolean[] selectedDays; private boolean[] selectedDays;
private OnWeekdaysPickedListener listener; private OnWeekdaysPickedListener listener;
@ -48,6 +51,21 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements
selectedDays[which] = isChecked; 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 @Override
public void onClick(DialogInterface dialog, int which) public void onClick(DialogInterface dialog, int which)
{ {

@ -19,7 +19,7 @@
package org.isoron.uhabits.activities.habits.edit; package org.isoron.uhabits.activities.habits.edit;
import android.app.Dialog; import android.app.*;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
@ -27,7 +27,7 @@ import android.support.v7.app.*;
import android.text.format.*; import android.text.format.*;
import android.view.*; import android.view.*;
import com.android.datetimepicker.time.*; import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
@ -40,7 +40,7 @@ import org.isoron.uhabits.core.preferences.*;
import butterknife.*; import butterknife.*;
import static android.view.View.GONE; import static android.view.View.*;
public class EditHabitDialog extends AppCompatDialogFragment public class EditHabitDialog extends AppCompatDialogFragment
{ {
@ -48,6 +48,8 @@ public class EditHabitDialog extends AppCompatDialogFragment
public static final String BUNDLE_HABIT_TYPE = "habitType"; public static final String BUNDLE_HABIT_TYPE = "habitType";
private static final String WEEKDAY_PICKER_TAG = "weekdayPicker";
protected Habit originalHabit; protected Habit originalHabit;
protected Preferences prefs; protected Preferences prefs;
@ -109,6 +111,8 @@ public class EditHabitDialog extends AppCompatDialogFragment
setupReminderController(); setupReminderController();
setupNameController(); setupNameController();
restoreChildFragmentListeners();
return view; return view;
} }
@ -268,8 +272,16 @@ public class EditHabitDialog extends AppCompatDialogFragment
WeekdayPickerDialog dialog = new WeekdayPickerDialog(); WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(reminderPanel); dialog.setListener(reminderPanel);
dialog.setSelectedDays(currentDays); 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);
}
} }

@ -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.
<b>Simple, beautiful and modern interface</b> <b>Beautiful, minimalistic and lightweight interface</b>
Loop has a minimalistic interface that is very easy to use and follows the material design guidelines. 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.
<b>Habit score</b> <b>Habit score</b>
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. 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.
<b>Detailed graphs and statistics</b>
Clearly see how your habits improved over time with detailed charts and statistics. Scroll back to see the complete history of your habits.
<b>Flexible schedules</b> <b>Flexible schedules</b>
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.
<b>Reminders</b> <b>Reminders</b>
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.
<b>Widgets</b> <b>Widgets</b>
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.
<b>Take control of your data</b>
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.
<b>No limitations</b>
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.
<b>Completely ad-free and open source</b> <b>Completely ad-free and open source</b>
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).
<b>Works offline and respects your privacy</b> <b>Works offline and respects your privacy</b>
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.
<b>Take your data with you</b>
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).

@ -1,6 +1,5 @@
1.8.2: 1.8.3
* Fix issues with reminders * Bugfixes
* Fix crash (HUAWEI)
1.8: 1.8:
* New bar chart showing number of repetitions performed each week, month or year * New bar chart showing number of repetitions performed each week, month or year
* Performing habits on irregular weekdays will no longer break your streak * Performing habits on irregular weekdays will no longer break your streak

@ -30,7 +30,6 @@ import static org.isoron.uhabits.core.utils.StringUtils.*;
public final class Timestamp public final class Timestamp
{ {
public static final long DAY_LENGTH = 86400000; public static final long DAY_LENGTH = 86400000;
public static final Timestamp ZERO = new Timestamp(0); public static final Timestamp ZERO = new Timestamp(0);
@ -39,10 +38,13 @@ public final class Timestamp
public Timestamp(long unixTime) public Timestamp(long unixTime)
{ {
if (unixTime < 0 || unixTime % DAY_LENGTH != 0) if (unixTime < 0)
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid unix time: " + unixTime); "Invalid unix time: " + unixTime);
if (unixTime % DAY_LENGTH != 0)
unixTime = (unixTime / DAY_LENGTH) * DAY_LENGTH;
this.unixTime = unixTime; this.unixTime = unixTime;
} }

@ -22,6 +22,7 @@ package org.isoron.uhabits.core.models;
import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.core.utils.*;
import org.junit.*; import org.junit.*;
import org.mockito.internal.verification.*;
import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertFalse;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
@ -64,5 +65,10 @@ public class TimestampTest extends BaseUnitTest
assertThat(t.daysUntil(t.minus(300)), equalTo(-300)); assertThat(t.daysUntil(t.minus(300)), equalTo(-300));
} }
@Test
public void testInexact() throws Exception
{
Timestamp t = new Timestamp(1578054764000L);
assertThat(t.getUnixTime(), equalTo(1578009600000L));
}
} }

Loading…
Cancel
Save