Merge pull request #547 from recheej/rechee/add_notes

Add Notes to Habits.
This commit is contained in:
2020-03-01 15:11:38 -05:00
committed by GitHub
39 changed files with 437 additions and 110 deletions

View File

@@ -87,6 +87,7 @@ dependencies {
implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.code.gson:gson:2.8.5"
implementation "com.google.code.findbugs:jsr305:3.0.2" implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
compileOnly "javax.annotation:jsr250-api:1.0" compileOnly "javax.annotation:jsr250-api:1.0"
compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION" compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -39,6 +39,7 @@ import static androidx.test.uiautomator.UiDevice.*;
public class BaseUserInterfaceTest public class BaseUserInterfaceTest
{ {
private static final String PKG = "org.isoron.uhabits"; private static final String PKG = "org.isoron.uhabits";
public static final String EMPTY_DESCRIPTION_HABIT_NAME = "Read books";
public static UiDevice device; public static UiDevice device;
@@ -96,25 +97,29 @@ public class BaseUserInterfaceTest
Habit h1 = fixtures.createEmptyHabit(); Habit h1 = fixtures.createEmptyHabit();
h1.setName("Wake up early"); h1.setName("Wake up early");
h1.setDescription("Did you wake up early today?"); h1.setQuestion("Did you wake up early today?");
h1.setDescription("test description 1");
h1.setColor(5); h1.setColor(5);
habitList.update(h1); habitList.update(h1);
Habit h2 = fixtures.createShortHabit(); Habit h2 = fixtures.createShortHabit();
h2.setName("Track time"); h2.setName("Track time");
h2.setDescription("Did you track your time?"); h2.setQuestion("Did you track your time?");
h2.setDescription("test description 2");
h2.setColor(5); h2.setColor(5);
habitList.update(h2); habitList.update(h2);
Habit h3 = fixtures.createLongHabit(); Habit h3 = fixtures.createLongHabit();
h3.setName("Meditate"); h3.setName("Meditate");
h3.setDescription("Did meditate today?"); h3.setQuestion("Did meditate today?");
h3.setDescription("test description 3");
h3.setColor(10); h3.setColor(10);
habitList.update(h3); habitList.update(h3);
Habit h4 = fixtures.createEmptyHabit(); Habit h4 = fixtures.createEmptyHabit();
h4.setName("Read books"); h4.setName(EMPTY_DESCRIPTION_HABIT_NAME);
h4.setDescription("Did you read books today?"); h4.setQuestion("Did you read books today?");
h4.setDescription("");
h4.setColor(2); h4.setColor(2);
habitList.update(h4); habitList.update(h4);
} }

View File

@@ -52,7 +52,8 @@ public class HabitFixtures
{ {
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName("Meditate"); habit.setName("Meditate");
habit.setDescription("Did you meditate this morning?"); habit.setQuestion("Did you meditate this morning?");
habit.setDescription("This is a test description");
habit.setColor(5); habit.setColor(5);
habit.setFrequency(Frequency.DAILY); habit.setFrequency(Frequency.DAILY);
habit.setId(id); habit.setId(id);
@@ -81,7 +82,7 @@ public class HabitFixtures
{ {
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName("Take a walk"); habit.setName("Take a walk");
habit.setDescription("How many steps did you walk today?"); habit.setQuestion("How many steps did you walk today?");
habit.setType(Habit.NUMBER_HABIT); habit.setType(Habit.NUMBER_HABIT);
habit.setTargetType(Habit.AT_LEAST); habit.setTargetType(Habit.AT_LEAST);
habit.setTargetValue(200.0); habit.setTargetValue(200.0);
@@ -103,7 +104,7 @@ public class HabitFixtures
{ {
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName("Wake up early"); habit.setName("Wake up early");
habit.setDescription("Did you wake up before 6am?"); habit.setQuestion("Did you wake up before 6am?");
habit.setFrequency(new Frequency(2, 3)); habit.setFrequency(new Frequency(2, 3));
habitList.add(habit); habitList.add(habit);

View File

@@ -20,7 +20,6 @@
package org.isoron.uhabits.acceptance; package org.isoron.uhabits.acceptance;
import androidx.test.filters.*; import androidx.test.filters.*;
import androidx.test.runner.*;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,7 +38,16 @@ import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*;
public class HabitsTest extends BaseUserInterfaceTest public class HabitsTest extends BaseUserInterfaceTest
{ {
@Test @Test
public void shouldCreateHabit() throws Exception public void shouldCreateHabit() throws Exception {
shouldCreateHabit("this is a test description");
}
@Test
public void shouldCreateHabitBlankDescription() throws Exception {
shouldCreateHabit("");
}
private void shouldCreateHabit(String description) throws Exception
{ {
launchApp(); launchApp();
@@ -47,14 +55,16 @@ public class HabitsTest extends BaseUserInterfaceTest
clickMenu(ADD); clickMenu(ADD);
verifyShowsScreen(EDIT_HABIT); verifyShowsScreen(EDIT_HABIT);
typeName("Hello world"); String testName = "Hello world";
typeName(testName);
typeQuestion("Did you say hello to the world today?"); typeQuestion("Did you say hello to the world today?");
typeDescription(description);
pickFrequency("Every week"); pickFrequency("Every week");
pickColor(5); pickColor(5);
clickSave(); clickSave();
verifyShowsScreen(LIST_HABITS); verifyShowsScreen(LIST_HABITS);
verifyDisplaysText("Hello world"); verifyDisplaysText(testName);
} }
@Test @Test
@@ -81,7 +91,16 @@ public class HabitsTest extends BaseUserInterfaceTest
} }
@Test @Test
public void shouldEditHabit() throws Exception public void shouldEditHabit() throws Exception {
shouldEditHabit("this is a test description");
}
@Test
public void shouldEditHabitBlankDescription() throws Exception {
shouldEditHabit("");
}
private void shouldEditHabit(String description) throws Exception
{ {
launchApp(); launchApp();
@@ -92,6 +111,7 @@ public class HabitsTest extends BaseUserInterfaceTest
verifyShowsScreen(EDIT_HABIT); verifyShowsScreen(EDIT_HABIT);
typeName("Take a walk"); typeName("Take a walk");
typeQuestion("Did you take a walk today?"); typeQuestion("Did you take a walk today?");
typeDescription(description);
clickSave(); clickSave();
verifyShowsScreen(LIST_HABITS); verifyShowsScreen(LIST_HABITS);
@@ -174,4 +194,12 @@ public class HabitsTest extends BaseUserInterfaceTest
verifyDisplaysText("Track time"); verifyDisplaysText("Track time");
verifyDisplaysText("Wake up early"); verifyDisplaysText("Wake up early");
} }
@Test
public void shouldHideNotesCard() throws Exception
{
launchApp();
clickText(EMPTY_DESCRIPTION_HABIT_NAME);
verifyShowsScreen(SHOW_HABIT, false);
}
} }

View File

@@ -19,14 +19,16 @@
package org.isoron.uhabits.acceptance.steps; package org.isoron.uhabits.acceptance.steps;
import android.view.View;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.test.espresso.*; import androidx.test.espresso.*;
import androidx.test.espresso.contrib.*; import androidx.test.espresso.contrib.*;
import androidx.test.uiautomator.*; import androidx.test.uiautomator.*;
import androidx.appcompat.widget.*;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.hamcrest.Matcher;
import org.isoron.uhabits.*; 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.*;
@@ -153,7 +155,11 @@ public class CommonSteps extends BaseUserInterfaceTest
LIST_HABITS, SHOW_HABIT, EDIT_HABIT LIST_HABITS, SHOW_HABIT, EDIT_HABIT
} }
public static void verifyShowsScreen(Screen screen) public static void verifyShowsScreen(Screen screen) {
verifyShowsScreen(screen, true);
}
public static void verifyShowsScreen(Screen screen, boolean notesCardVisibleExpected)
{ {
switch(screen) switch(screen)
{ {
@@ -163,10 +169,14 @@ public class CommonSteps extends BaseUserInterfaceTest
break; break;
case SHOW_HABIT: case SHOW_HABIT:
Matcher<View> noteCardViewMatcher = notesCardVisibleExpected ? isDisplayed() :
withEffectiveVisibility(Visibility.GONE);
onView(withId(R.id.subtitleCard)).check(matches(isDisplayed())); onView(withId(R.id.subtitleCard)).check(matches(isDisplayed()));
onView(withId(R.id.notesCard)).check(matches(noteCardViewMatcher));
break; break;
case EDIT_HABIT: case EDIT_HABIT:
onView(withId(R.id.tvQuestion)).check(matches(isDisplayed()));
onView(withId(R.id.tvDescription)).check(matches(isDisplayed())); onView(withId(R.id.tvDescription)).check(matches(isDisplayed()));
break; break;
} }

View File

@@ -55,7 +55,12 @@ public class EditHabitSteps
public static void typeQuestion(String name) public static void typeQuestion(String name)
{ {
typeTextWithId(R.id.tvDescription, name); typeTextWithId(R.id.tvQuestion, name);
}
public static void typeDescription(String description)
{
typeTextWithId(R.id.tvDescription, description);
} }
public static void setReminder() public static void setReminder()

View File

@@ -0,0 +1,79 @@
/*
* 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.show.views;
import android.view.LayoutInflater;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import org.isoron.uhabits.BaseViewTest;
import org.isoron.uhabits.R;
import org.isoron.uhabits.core.models.Habit;
import org.isoron.uhabits.core.models.Reminder;
import org.isoron.uhabits.core.models.WeekdayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class NotesCardTest extends BaseViewTest
{
public static final String PATH = "habits/show/NotesCard/";
private NotesCard view;
private Habit habit;
@Before
@Override
public void setUp()
{
super.setUp();
habit = fixtures.createLongHabit();
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
view = LayoutInflater
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.notesCard);
view.setHabit(habit);
view.refreshData();
measureView(view, 800, 200);
}
@Test
public void testRender() throws Exception
{
assertRenders(view, PATH + "render.png");
}
@Test
public void testRenderEmptyDescription() throws Exception
{
habit.setDescription("");
view.refreshData();
assertRenders(view, PATH + "render-empty-description.png");
}
}

View File

@@ -49,7 +49,7 @@ public class SubtitleCardTest extends BaseViewTest
habit = fixtures.createLongHabit(); habit = fixtures.createLongHabit();
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
view = (SubtitleCard) LayoutInflater view = LayoutInflater
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById(R.id.subtitleCard); .findViewById(R.id.subtitleCard);

View File

@@ -187,6 +187,7 @@ public class EditHabitDialog extends AppCompatDialogFragment
habit.copyFrom(originalHabit); habit.copyFrom(originalHabit);
habit.setName(namePanel.getName()); habit.setName(namePanel.getName());
habit.setDescription(namePanel.getDescription()); habit.setDescription(namePanel.getDescription());
habit.setQuestion(namePanel.getQuestion());
habit.setColor(namePanel.getColor()); habit.setColor(namePanel.getColor());
habit.setReminder(reminderPanel.getReminder()); habit.setReminder(reminderPanel.getReminder());
habit.setFrequency(frequencyPanel.getFrequency()); habit.setFrequency(frequencyPanel.getFrequency());

View File

@@ -24,6 +24,7 @@ import android.text.*;
import android.util.*; import android.util.*;
import android.view.*; import android.view.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText; import androidx.appcompat.widget.AppCompatEditText;
@@ -82,7 +83,7 @@ public class ExampleEditText extends AppCompatEditText
updateText(); updateText();
} }
public void setRealText(String realText) public void setRealText(@NonNull String realText)
{ {
this.realText = realText; this.realText = realText;
updateText(); updateText();

View File

@@ -42,6 +42,9 @@ public class NameDescriptionPanel extends FrameLayout
@BindView(R.id.tvName) @BindView(R.id.tvName)
EditText tvName; EditText tvName;
@BindView(R.id.tvQuestion)
ExampleEditText tvQuestion;
@BindView(R.id.tvDescription) @BindView(R.id.tvDescription)
ExampleEditText tvDescription; ExampleEditText tvDescription;
@@ -79,6 +82,12 @@ public class NameDescriptionPanel extends FrameLayout
return tvDescription.getRealText().trim(); return tvDescription.getRealText().trim();
} }
@NonNull
public String getQuestion()
{
return tvQuestion.getRealText().trim();
}
@NonNull @NonNull
public String getName() public String getName()
{ {
@@ -90,12 +99,13 @@ public class NameDescriptionPanel extends FrameLayout
Resources res = getResources(); Resources res = getResources();
if(habit.isNumerical()) if(habit.isNumerical())
tvDescription.setExample(res.getString(R.string.example_question_numerical)); tvQuestion.setExample(res.getString(R.string.example_question_numerical));
else else
tvDescription.setExample(res.getString(R.string.example_question_boolean)); tvQuestion.setExample(res.getString(R.string.example_question_boolean));
setColor(habit.getColor()); setColor(habit.getColor());
tvName.setText(habit.getName()); tvName.setText(habit.getName());
tvQuestion.setRealText(habit.getQuestion());
tvDescription.setRealText(habit.getDescription()); tvDescription.setRealText(habit.getDescription());
} }

View File

@@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.show;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.*; import androidx.appcompat.widget.*;
@@ -52,6 +53,12 @@ public class ShowHabitRootView extends BaseRootView
@BindView(R.id.subtitleCard) @BindView(R.id.subtitleCard)
SubtitleCard subtitleCard; SubtitleCard subtitleCard;
@BindView(R.id.notesCard)
NotesCard notesCard;
@BindView(R.id.habitNotes)
TextView habitNotes;
@BindView(R.id.overviewCard) @BindView(R.id.overviewCard)
OverviewCard overviewCard; OverviewCard overviewCard;
@@ -136,6 +143,7 @@ public class ShowHabitRootView extends BaseRootView
private void initCards() private void initCards()
{ {
subtitleCard.setHabit(habit); subtitleCard.setHabit(habit);
notesCard.setHabit(habit);
overviewCard.setHabit(habit); overviewCard.setHabit(habit);
scoreCard.setHabit(habit); scoreCard.setHabit(habit);
historyCard.setHabit(habit); historyCard.setHabit(habit);

View File

@@ -0,0 +1,26 @@
package org.isoron.uhabits.activities.habits.show.views
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import org.isoron.uhabits.R
import org.isoron.uhabits.core.tasks.Task
class NotesCard(context: Context?, attrs: AttributeSet?) : HabitCard(context, attrs) {
private val notesTextView: TextView
init {
View.inflate(getContext(), R.layout.show_habit_notes, this)
notesTextView = findViewById(R.id.habitNotes)
}
override fun refreshData() {
notesTextView.text = habit.description
visibility = if(habit.description.isEmpty()) View.GONE else View.VISIBLE
notesTextView.visibility = visibility
}
override fun createRefreshTask(): Task = error("refresh task should never be called.")
}

View File

@@ -59,12 +59,12 @@ public class SubtitleCard extends HabitCard
questionLabel.setVisibility(VISIBLE); questionLabel.setVisibility(VISIBLE);
questionLabel.setTextColor(color); questionLabel.setTextColor(color);
questionLabel.setText(habit.getDescription()); questionLabel.setText(habit.getQuestion());
frequencyLabel.setText(toText(habit.getFrequency())); frequencyLabel.setText(toText(habit.getFrequency()));
if (habit.hasReminder()) updateReminderText(habit.getReminder()); if (habit.hasReminder()) updateReminderText(habit.getReminder());
if (habit.getDescription().isEmpty()) questionLabel.setVisibility(GONE); if (habit.getQuestion().isEmpty()) questionLabel.setVisibility(GONE);
invalidate(); invalidate();
} }

View File

@@ -112,7 +112,7 @@ class AndroidNotificationTray
val builder = NotificationCompat.Builder(context, REMINDERS_CHANNEL_ID) val builder = NotificationCompat.Builder(context, REMINDERS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name) .setContentTitle(habit.name)
.setContentText(if(habit.description.isBlank()) defaultText else habit.description) .setContentText(if(habit.question.isBlank()) defaultText else habit.question)
.setContentIntent(pendingIntents.showHabit(habit)) .setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit)) .setDeleteIntent(pendingIntents.dismissNotification(habit))
.addAction(addRepetitionAction) .addAction(addRepetitionAction)

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> ~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~ ~
~ This file is part of Loop Habit Tracker. ~ This file is part of Loop Habit Tracker.
@@ -18,27 +17,29 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>. ~ with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://isoron.org/android" xmlns:app="http://isoron.org/android"
xmlns:app1="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:minWidth="300dp">
<LinearLayout
style="@style/dialogFormRow">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilName" android:id="@+id/tilName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="6"> app1:layout_constraintEnd_toStartOf="@+id/buttonPickColor"
app1:layout_constraintHorizontal_weight="6"
app1:layout_constraintStart_toStartOf="parent"
app1:layout_constraintTop_toTopOf="parent">
<EditText <EditText
android:id="@+id/tvName" android:id="@+id/tvName"
style="@style/dialogFormInput" style="@style/dialogFormInput"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="start"
android:gravity="center_vertical"
android:hint="@string/name"> android:hint="@string/name">
<requestFocus /> <requestFocus />
@@ -48,23 +49,50 @@
<ImageButton <ImageButton
android:id="@+id/buttonPickColor" android:id="@+id/buttonPickColor"
style="@style/dialogFormInputColor" style="@style/dialogFormInputColor"
android:layout_weight="1" android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/color_picker_default_title" android:contentDescription="@string/color_picker_default_title"
android:src="?dialogIconChangeColor"/> android:src="?dialogIconChangeColor"
app1:layout_constraintBottom_toBottomOf="@id/tilName"
</LinearLayout> 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 <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_width="match_parent"
android:layout_height="wrap_content"> 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 <org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
android:id="@+id/tvDescription" android:id="@+id/tvDescription"
style="@style/dialogFormInputMultiline" style="@style/dialogFormInputMultiline"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/question" android:gravity="top"
app:example="@string/example_question_numerical"/> app:example="@string/example_notes" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -38,6 +38,11 @@
android:id="@+id/subtitleCard" android:id="@+id/subtitleCard"
style="@style/ShowHabit.Subtitle"/> style="@style/ShowHabit.Subtitle"/>
<org.isoron.uhabits.activities.habits.show.views.NotesCard
android:id="@+id/notesCard"
style="@style/Card"
android:gravity="center" />
<View <View
android:id="@+id/headerShadow" android:id="@+id/headerShadow"
style="@style/ToolbarShadow"/> style="@style/ToolbarShadow"/>

View File

@@ -0,0 +1,35 @@
<?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/>.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:layout_height="wrap_content"
tools:layout_width="match_parent"
tools:orientation="vertical"
tools:parentTag="android.widget.LinearLayout">
<TextView
android:id="@+id/habitNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?highContrastTextColor"
android:visibility="gone"
tools:text="This is some example text for the notes" />
</merge>

View File

@@ -18,7 +18,12 @@
~ with this program. If not, see <http://www.gnu.org/licenses/>. ~ with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.LinearLayout"
tools:orientation="vertical"
tools:layout_width="match_parent"
tools:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/questionLabel" android:id="@+id/questionLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -26,6 +31,7 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:textColor="?mediumContrastTextColor" android:textColor="?mediumContrastTextColor"
android:textSize="@dimen/regularTextSize" android:textSize="@dimen/regularTextSize"
tools:text="Have you worked out today?"
/> />
<LinearLayout <LinearLayout
@@ -33,7 +39,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal"
tools:visibility="visible">
<ImageView <ImageView
android:id="@+id/frequencyIcon" android:id="@+id/frequencyIcon"
@@ -42,8 +49,7 @@
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:layout_marginRight="5dp" android:layout_marginRight="5dp"
android:alpha="0.3" android:alpha="0.3"
android:src="?iconFrequency" android:src="?iconFrequency" />
/>
<TextView <TextView
android:id="@+id/frequencyLabel" android:id="@+id/frequencyLabel"
@@ -51,20 +57,18 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/every_day" android:text="@string/every_day"
android:textColor="?mediumContrastTextColor" android:textColor="?mediumContrastTextColor"
android:textSize="@dimen/smallTextSize" android:textSize="@dimen/smallTextSize" />
/>
<ImageView <ImageView
android:id="@+id/reminderIcon" android:id="@+id/reminderIcon"
android:layout_width="18dp" android:layout_width="18dp"
android:layout_height="18dp" android:layout_height="18dp"
android:layout_marginEnd="5dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="5dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:alpha="0.3" android:alpha="0.3"
android:src="?iconReminder" android:src="?iconReminder" />
/>
<TextView <TextView
android:id="@+id/reminderLabel" android:id="@+id/reminderLabel"
@@ -72,8 +76,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="1dp" android:paddingTop="1dp"
android:textColor="?mediumContrastTextColor" android:textColor="?mediumContrastTextColor"
android:textSize="@dimen/smallTextSize" android:textSize="@dimen/smallTextSize" />
/>
</LinearLayout> </LinearLayout>
</merge> </merge>

View File

@@ -244,5 +244,7 @@
<string name="widget_opacity_description">Makes widgets more transparent or more opaque in your home screen.</string> <string name="widget_opacity_description">Makes widgets more transparent or more opaque in your home screen.</string>
<string name="first_day_of_the_week">First day of the week</string> <string name="first_day_of_the_week">First day of the week</string>
<string name="default_reminder_question">Have you completed this habit today?</string> <string name="default_reminder_question">Have you completed this habit today?</string>
<string name="notes">Notes</string>
<string name="example_notes">You can put whatever you want here!</string>
</resources> </resources>

View File

@@ -1,5 +1,6 @@
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'kotlin'
dependencies { dependencies {
annotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION" annotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
@@ -26,6 +27,7 @@ dependencies {
implementation('com.opencsv:opencsv:3.10') { implementation('com.opencsv:opencsv:3.10') {
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
} }
sourceCompatibility = "1.8" sourceCompatibility = "1.8"

View File

@@ -22,5 +22,5 @@ package org.isoron.uhabits.core;
public class Config public class Config
{ {
public static final String DATABASE_FILENAME = "uhabits.db"; public static final String DATABASE_FILENAME = "uhabits.db";
public static int DATABASE_VERSION = 22; public static int DATABASE_VERSION = 23;
} }

View File

@@ -87,7 +87,7 @@ public class HabitBullCSVImporter extends AbstractImporter
{ {
h = modelFactory.buildHabit(); h = modelFactory.buildHabit();
h.setName(name); h.setName(name);
h.setDescription(description); h.setDescription(description == null ? "" : description);
h.setFrequency(Frequency.DAILY); h.setFrequency(Frequency.DAILY);
habitList.add(h); habitList.add(h);
map.put(name, h); map.put(name, h);

View File

@@ -101,7 +101,7 @@ public class RewireDBImporter extends AbstractImporter
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName(name); habit.setName(name);
habit.setDescription(description); habit.setDescription(description == null ? "" : description);
int periods[] = { 7, 31, 365 }; int periods[] = { 7, 31, 365 };
int numerator, denominator; int numerator, denominator;

View File

@@ -127,7 +127,7 @@ public class TickmateDBImporter extends AbstractImporter
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName(name); habit.setName(name);
habit.setDescription(description); habit.setDescription(description == null ? "" : description);
habit.setFrequency(Frequency.DAILY); habit.setFrequency(Frequency.DAILY);
habitList.add(habit); habitList.add(habit);

View File

@@ -354,6 +354,15 @@ public class Habit
data.position = newPosition; data.position = newPosition;
} }
@NonNull
public String getQuestion() {
return data.question;
}
public void setQuestion(@NonNull String question) {
data.question = question;
}
public static final class HabitData public static final class HabitData
{ {
@NonNull @NonNull
@@ -362,6 +371,9 @@ public class Habit
@NonNull @NonNull
public String description; public String description;
@NonNull
public String question;
@NonNull @NonNull
public Frequency frequency; public Frequency frequency;
@@ -391,6 +403,7 @@ public class Habit
this.type = YES_NO_HABIT; this.type = YES_NO_HABIT;
this.name = ""; this.name = "";
this.description = ""; this.description = "";
this.question = "";
this.targetType = AT_LEAST; this.targetType = AT_LEAST;
this.targetValue = 100; this.targetValue = 100;
this.unit = ""; this.unit = "";
@@ -401,6 +414,7 @@ public class Habit
{ {
this.name = model.name; this.name = model.name;
this.description = model.description; this.description = model.description;
this.question = model.question;
this.frequency = model.frequency; this.frequency = model.frequency;
this.color = model.color; this.color = model.color;
this.archived = model.archived; this.archived = model.archived;
@@ -427,6 +441,7 @@ public class Habit
.append("unit", unit) .append("unit", unit)
.append("reminder", reminder) .append("reminder", reminder)
.append("position", position) .append("position", position)
.append("question", question)
.toString(); .toString();
} }
@@ -451,6 +466,7 @@ public class Habit
.append(unit, habitData.unit) .append(unit, habitData.unit)
.append(reminder, habitData.reminder) .append(reminder, habitData.reminder)
.append(position, habitData.position) .append(position, habitData.position)
.append(question, habitData.question)
.isEquals(); .isEquals();
} }
@@ -469,6 +485,7 @@ public class Habit
.append(unit) .append(unit)
.append(reminder) .append(reminder)
.append(position) .append(position)
.append(question)
.toHashCode(); .toHashCode();
} }
} }

View File

@@ -212,6 +212,7 @@ public abstract class HabitList implements Iterable<Habit>
String header[] = { String header[] = {
"Position", "Position",
"Name", "Name",
"Question",
"Description", "Description",
"NumRepetitions", "NumRepetitions",
"Interval", "Interval",
@@ -228,6 +229,7 @@ public abstract class HabitList implements Iterable<Habit>
String[] cols = { String[] cols = {
String.format("%03d", indexOf(habit) + 1), String.format("%03d", indexOf(habit) + 1),
habit.getName(), habit.getName(),
habit.getQuestion(),
habit.getDescription(), habit.getDescription(),
Integer.toString(freq.getNumerator()), Integer.toString(freq.getNumerator()),
Integer.toString(freq.getDenominator()), Integer.toString(freq.getDenominator()),

View File

@@ -33,6 +33,9 @@ public class HabitRecord
@Column @Column
public String description; public String description;
@Column
public String question;
@Column @Column
public String name; public String name;
@@ -91,6 +94,7 @@ public class HabitRecord
this.targetValue = model.getTargetValue(); this.targetValue = model.getTargetValue();
this.unit = model.getUnit(); this.unit = model.getUnit();
this.position = model.getPosition(); this.position = model.getPosition();
this.question = model.getQuestion();
Frequency freq = model.getFrequency(); Frequency freq = model.getFrequency();
this.freqNum = freq.getNumerator(); this.freqNum = freq.getNumerator();
@@ -113,6 +117,7 @@ public class HabitRecord
habit.setId(this.id); habit.setId(this.id);
habit.setName(this.name); habit.setName(this.name);
habit.setDescription(this.description); habit.setDescription(this.description);
habit.setQuestion(this.question);
habit.setFrequency(new Frequency(this.freqNum, this.freqDen)); habit.setFrequency(new Frequency(this.freqNum, this.freqDen));
habit.setColor(this.color); habit.setColor(this.color);
habit.setArchived(this.archived != 0); habit.setArchived(this.archived != 0);

View File

@@ -43,7 +43,7 @@ public class HabitFixtures
{ {
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName("Meditate"); habit.setName("Meditate");
habit.setDescription("Did you meditate this morning?"); habit.setQuestion("Did you meditate this morning?");
habit.setColor(3); habit.setColor(3);
habit.setFrequency(Frequency.DAILY); habit.setFrequency(Frequency.DAILY);
saveIfSQLite(habit); saveIfSQLite(habit);
@@ -73,7 +73,7 @@ public class HabitFixtures
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setType(Habit.NUMBER_HABIT); habit.setType(Habit.NUMBER_HABIT);
habit.setName("Run"); habit.setName("Run");
habit.setDescription("How many miles did you run today?"); habit.setQuestion("How many miles did you run today?");
habit.setUnit("miles"); habit.setUnit("miles");
habit.setTargetType(Habit.AT_LEAST); habit.setTargetType(Habit.AT_LEAST);
habit.setTargetValue(2.0); habit.setTargetValue(2.0);
@@ -98,7 +98,7 @@ public class HabitFixtures
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setType(Habit.NUMBER_HABIT); habit.setType(Habit.NUMBER_HABIT);
habit.setName("Walk"); habit.setName("Walk");
habit.setDescription("How many steps did you walk today?"); habit.setQuestion("How many steps did you walk today?");
habit.setUnit("steps"); habit.setUnit("steps");
habit.setTargetType(Habit.AT_LEAST); habit.setTargetType(Habit.AT_LEAST);
habit.setTargetValue(100); habit.setTargetValue(100);
@@ -133,7 +133,7 @@ public class HabitFixtures
{ {
Habit habit = modelFactory.buildHabit(); Habit habit = modelFactory.buildHabit();
habit.setName("Wake up early"); habit.setName("Wake up early");
habit.setDescription("Did you wake up before 6am?"); habit.setQuestion("Did you wake up before 6am?");
habit.setFrequency(new Frequency(2, 3)); habit.setFrequency(new Frequency(2, 3));
saveIfSQLite(habit); saveIfSQLite(habit);

View File

@@ -0,0 +1,5 @@
alter table Habits add column question text;
update Habits set question = description;
update Habits set description = "";

View File

@@ -125,7 +125,7 @@ public class BaseUnitTest
DriverManager.getConnection("jdbc:sqlite::memory:")); DriverManager.getConnection("jdbc:sqlite::memory:"));
db.execute("pragma user_version=8;"); db.execute("pragma user_version=8;");
MigrationHelper helper = new MigrationHelper(db); MigrationHelper helper = new MigrationHelper(db);
helper.migrateTo(21); helper.migrateTo(23);
return db; return db;
} }
catch (SQLException e) catch (SQLException e)

View File

@@ -159,21 +159,4 @@ public class Version22Test extends BaseUnitTest
db.execute("insert into repetitions(habit, timestamp, value)" + db.execute("insert into repetitions(habit, timestamp, value)" +
"values (0, 100, 5)"); "values (0, 100, 5)");
} }
@Test
public void testKeepHabitsUnchanged() throws Exception
{
Habit original = fixtures.createLongHabit();
Reminder reminder = new Reminder(8, 30, new WeekdayList(100));
original.setReminder(reminder);
habitList.update(original);
helper.migrateTo(22);
((SQLiteHabitList) habitList).reload();
Habit modified = habitList.getById(original.getId());
assertNotNull(modified);
assertThat(original.getData(), equalTo(modified.getData()));
}
} }

View File

@@ -0,0 +1,63 @@
package org.isoron.uhabits.core.database.migrations
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.database.Database
import org.isoron.uhabits.core.database.MigrationHelper
import org.isoron.uhabits.core.models.sqlite.SQLModelFactory
import org.isoron.uhabits.core.test.HabitFixtures
import org.junit.Test
class Version23Test: BaseUnitTest() {
private lateinit var db: Database
private lateinit var helper: MigrationHelper
override fun setUp() {
super.setUp()
db = openDatabaseResource("/databases/022.db")
helper = MigrationHelper(db)
modelFactory = SQLModelFactory(db)
habitList = modelFactory.buildHabitList()
fixtures = HabitFixtures(modelFactory, habitList)
}
private fun migrateTo23() = helper.migrateTo(23)
@Test
fun `test migrate to 23 creates question column`() {
migrateTo23()
val cursor = db.query("select question from Habits")
cursor.moveToNext()
}
@Test
fun `test migrate to 23 moves description to question column`() {
var cursor = db.query("select description from Habits")
val descriptions = mutableListOf<String?>()
while(cursor.moveToNext()){
descriptions.add(cursor.getString(0))
}
migrateTo23()
cursor = db.query("select question from Habits")
for(i in 0 until descriptions.size){
cursor.moveToNext()
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(descriptions[i]))
}
}
@Test
fun `test migrate to 23 sets description to null`() {
migrateTo23()
val cursor = db.query("select description from Habits")
while(cursor.moveToNext()){
MatcherAssert.assertThat(cursor.getString(0), Matchers.equalTo(""))
}
}
}

View File

@@ -218,13 +218,15 @@ public class HabitListTest extends BaseUnitTest
Habit h1 = fixtures.createEmptyHabit(); Habit h1 = fixtures.createEmptyHabit();
h1.setName("Meditate"); h1.setName("Meditate");
h1.setDescription("Did you meditate this morning?"); h1.setQuestion("Did you meditate this morning?");
h1.setDescription("this is a test description");
h1.setFrequency(Frequency.DAILY); h1.setFrequency(Frequency.DAILY);
h1.setColor(3); h1.setColor(3);
Habit h2 = fixtures.createEmptyHabit(); Habit h2 = fixtures.createEmptyHabit();
h2.setName("Wake up early"); h2.setName("Wake up early");
h2.setDescription("Did you wake up before 6am?"); h2.setQuestion("Did you wake up before 6am?");
h2.setDescription("");
h2.setFrequency(new Frequency(2, 3)); h2.setFrequency(new Frequency(2, 3));
h2.setColor(5); h2.setColor(5);
@@ -232,9 +234,9 @@ public class HabitListTest extends BaseUnitTest
list.add(h2); list.add(h2);
String expectedCSV = String expectedCSV =
"Position,Name,Description,NumRepetitions,Interval,Color\n" + "Position,Name,Question,Description,NumRepetitions,Interval,Color\n" +
"001,Meditate,Did you meditate this morning?,1,1,#FF8F00\n" + "001,Meditate,Did you meditate this morning?,this is a test description,1,1,#FF8F00\n" +
"002,Wake up early,Did you wake up before 6am?,2,3,#AFB42B\n"; "002,Wake up early,Did you wake up before 6am?,,2,3,#AFB42B\n";
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
list.writeCSV(writer); list.writeCSV(writer);

View File

@@ -155,7 +155,7 @@ public class HabitTest extends BaseUnitTest
" targetValue: 100.0, type: 0, unit: ," + " targetValue: 100.0, type: 0, unit: ," +
" reminder: {hour: 22, minute: 30," + " reminder: {hour: 22, minute: 30," +
" days: {weekdays: [true,true,true,true,true,true,true]}}," + " days: {weekdays: [true,true,true,true,true,true,true]}}," +
" position: 0}}"; " position: 0, question: }}";
assertThat(h.toString(), equalTo(expected)); assertThat(h.toString(), equalTo(expected));
} }

View File

@@ -36,7 +36,7 @@ public class HabitRecordTest extends BaseUnitTest
{ {
Habit original = modelFactory.buildHabit(); Habit original = modelFactory.buildHabit();
original.setName("Hello world"); original.setName("Hello world");
original.setDescription("Did you greet the world today?"); original.setQuestion("Did you greet the world today?");
original.setColor(1); original.setColor(1);
original.setArchived(true); original.setArchived(true);
original.setFrequency(Frequency.THREE_TIMES_PER_WEEK); original.setFrequency(Frequency.THREE_TIMES_PER_WEEK);
@@ -58,7 +58,7 @@ public class HabitRecordTest extends BaseUnitTest
{ {
Habit original = modelFactory.buildHabit(); Habit original = modelFactory.buildHabit();
original.setName("Hello world"); original.setName("Hello world");
original.setDescription("Did you greet the world today?"); original.setQuestion("Did you greet the world today?");
original.setColor(5); original.setColor(5);
original.setArchived(false); original.setArchived(false);
original.setFrequency(Frequency.DAILY); original.setFrequency(Frequency.DAILY);