diff --git a/CHANGELOG.md b/CHANGELOG.md index 31acf8f74..f4dd8bd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +### 1.3.0 (March 12, 2016) + +* New frequency plot: view total repetitions per day of week +* New history editor: put checkmarks in the past +* German, French and Japanese translations +* Add about screen, with credits to all contributors +* Fix small bug that prevented habit from being reordered +* Fix small bug caused by rotating the device + ### 1.2.0 (March 4, 2016) * Ability to export habit data as CSV diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 000000000..12368d1e6 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,91 @@ +# Copyright Notices + +### ActiveAndroid + + + + Copyright (C) 2010 Michael Pardo + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +### Android Open Source Project + + + + Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +### FontAwesome + + + +Font Awesome is a full suite of 605 pictographic icons for easy scalable +vector graphics on websites, created and maintained by Dave Gandy. Licensed +under the SIL OFL 1.1. + + The goals of the Open Font License (OFL) are to stimulate worldwide + development of collaborative font projects, to support the font creation + efforts of academic and linguistic communities, and to provide a free and + open framework in which fonts may be shared and improved in partnership + with others. + + The OFL allows the licensed fonts to be used, studied, modified and + redistributed freely as long as they are not sold by themselves. The + fonts, including any derivative works, can be bundled, embedded, + redistributed and/or sold with any software provided that any reserved + names are not used by derivative works. The fonts and derivatives, + however, cannot be released under any other type of license. The + requirement for fonts to remain under this license does not apply + to any document created using the fonts or their derivatives. + + +### DragSortListView + + + + A subclass of the Android ListView component that enables drag + and drop re-ordering of list items. + + Copyright 2012 Carl Bauer + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +### Material Design Icons + + + +Material design icons are the official icon set from Google that are designed +under the material design guidelines. Available under the Creative Common +Attribution 4.0 International License (CC-BY 4.0). + diff --git a/README.md b/README.md index 065430702..e3c7796c8 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,43 @@ # Loop Habit Tracker -Loop is a simple Android app that helps you create and maintain good habits, allowing you to achieve your long-term goals. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source. +Loop is a simple Android app that helps you create and maintain good habits, +allowing you to achieve your long-term goals. Detailed graphs and statistics +show you how your habits improved over time. It is completely ad-free and open +source. -Get it on Google Play +Get it on Google Play +Git if on F-Droid ## Features -Simple, beautiful and modern interface -Loop has a minimalistic interface that is easy to use and follows the material design guidelines. +* **Simple, beautiful and modern interface.** Loop has a minimalistic interface + that is easy to use and follows the material design guidelines. -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.** 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. -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. +* **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 -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. +* **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 -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. +* **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. -Optimized for smartwatches -Reminders can be checked, snoozed or dismissed directly from your Android Wear watch. +* **Optimized for smartwatches.** Reminders can be checked, snoozed or + dismissed directly from your Android Wear watch. -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. +* **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. ## Screenshots @@ -35,6 +47,46 @@ There are absolutely no advertisements, annoying notifications or intrusive perm [![Habit history and streaks][screen4th]][screen4] [![Widgets][screen5th]][screen5] +## Installing + +The easiest way to install Loop is through the [Google Play Store][playstore] or [F-Droid][fdroid]. +You may also download and install the APK from the [releases page][releases]; +note, however, that the app will not be updated automatically. To build this +app from the source code, see [building instructions][build]. + +## Contributing + +Loop is an open source project developed entirely by volunteers. If you would +like to contribute to the project, you are very welcome. There are many ways to +contribute, even if you are not a software developer. + +* **Report bugs, suggest features.** The easiest way to contribute is to simply + use the app and let us know if you find any problems or have any suggestions + to improve it. You can either use the link inside the app, or open an issue + at GitHub. + +* **Translate the app into your own language.** If you are not a native English + speaker, and would like to see the app translated into your own language, + please join our [open translation project at POEditor][poedit]. + +* **Write some code.** If you are an Android developer, you are very welcome to + contribute with code. Please, see the [developer guidelines][dev-guide] for more details. + +## License + + This program 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. + + This program 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 . + [screen1]: screenshots/original/uhabits1.png [screen2]: screenshots/original/uhabits2.png [screen3]: screenshots/original/uhabits3.png @@ -45,3 +97,9 @@ There are absolutely no advertisements, annoying notifications or intrusive perm [screen3th]: screenshots/thumbs/uhabits3.png [screen4th]: screenshots/thumbs/uhabits4.png [screen5th]: screenshots/thumbs/uhabits5.png +[poedit]: https://poeditor.com/join/project/8DWX5pfjS0 +[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits +[releases]: https://github.com/iSoron/uhabits/releases +[fdroid]: http://f-droid.org/app/org.isoron.uhabits +[dev-guide]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines +[build]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines#building diff --git a/app/build.gradle b/app/build.gradle index cb85919a2..a1a1bb4bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,6 +8,8 @@ android { applicationId "org.isoron.uhabits" minSdkVersion 15 targetSdkVersion 23 + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -15,6 +17,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } + debug { + testCoverageEnabled = true + } } lintOptions { @@ -27,5 +32,11 @@ dependencies { compile 'com.github.paolorotolo:appintro:3.4.0' compile project(':libs:drag-sort-listview:library') compile files('libs/ActiveAndroid.jar') + + androidTestCompile 'com.android.support:support-annotations:23.1.1' + androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:rules:0.4.1' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' + androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1' } diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java b/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java new file mode 100644 index 000000000..c96b24c16 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Á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; + +import android.view.View; +import android.widget.Adapter; +import android.widget.AdapterView; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.isoron.uhabits.models.Habit; + +public class HabitMatchers +{ + public static Matcher withName(final String name) + { + return new TypeSafeMatcher() + { + @Override + public boolean matchesSafely(Habit habit) + { + return habit.name.equals(name); + } + + @Override + public void describeTo(Description description) + { + description.appendText("name should be ").appendText(name); + } + + @Override + public void describeMismatchSafely(Habit habit, Description description) + { + description.appendText("was ").appendText(habit.name); + } + }; + } + + public static Matcher containsHabit(final Matcher matcher) + { + return new TypeSafeMatcher() + { + @Override + protected boolean matchesSafely(View view) + { + Adapter adapter = ((AdapterView) view).getAdapter(); + for (int i = 0; i < adapter.getCount(); i++) + if (matcher.matches(adapter.getItem(i))) return true; + + return false; + } + + @Override + public void describeTo(Description description) + { + description.appendText("with class name: "); + matcher.describeTo(description); + } + }; + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java new file mode 100644 index 000000000..7c9a7e0a2 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 Á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; + +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.action.CoordinatesProvider; +import android.support.test.espresso.action.GeneralClickAction; +import android.support.test.espresso.action.GeneralLocation; +import android.support.test.espresso.action.Press; +import android.support.test.espresso.action.Tap; +import android.support.test.espresso.matcher.ViewMatchers; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.hamcrest.Matcher; + +import java.security.InvalidParameterException; +import java.util.Random; + +public class HabitViewActions +{ + public static ViewAction toggleAllCheckmarks() + { + final GeneralClickAction clickAction = + new GeneralClickAction(Tap.LONG, GeneralLocation.CENTER, Press.FINGER); + + return new ViewAction() + { + @Override + public Matcher getConstraints() + { + return ViewMatchers.isDisplayed(); + } + + @Override + public String getDescription() + { + return "toggleAllCheckmarks"; + } + + @Override + public void perform(UiController uiController, View view) + { + if (view.getId() != R.id.llButtons) + throw new InvalidParameterException("View must have id llButtons"); + + LinearLayout llButtons = (LinearLayout) view; + int count = llButtons.getChildCount(); + + for (int i = 0; i < count; i++) + { + TextView tvButton = (TextView) llButtons.getChildAt(i); + clickAction.perform(uiController, tvButton); + } + } + }; + } + + public static ViewAction clickAt(final int x, final int y) + { + return new GeneralClickAction(Tap.SINGLE, new CoordinatesProvider() + { + @Override + public float[] calculateCoordinates(View view) + { + int[] locations = new int[2]; + view.getLocationOnScreen(locations); + + final float locationX = locations[0] + x; + final float locationY = locations[1] + y; + + return new float[]{locationX, locationY}; + } + }, Press.FINGER); + } + + public static ViewAction clickAtRandomLocations(final int count) + { + return new ViewAction() + { + @Override + public Matcher getConstraints() + { + return ViewMatchers.isDisplayed(); + } + + @Override + public String getDescription() + { + return "clickAtRandomLocations"; + } + + @Override + public void perform(UiController uiController, View view) + { + int width = view.getWidth(); + int height = view.getHeight(); + Random random = new Random(); + + for(int i = 0; i < count; i++) + { + int x = random.nextInt(width); + int y = random.nextInt(height); + + ViewAction action = clickAt(x, y); + action.perform(uiController, view); + } + } + }; + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java new file mode 100644 index 000000000..3ce26da37 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 Á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; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import org.isoron.uhabits.models.Habit; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.openContextualActionModeOverflowMenu; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.replaceText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.isoron.uhabits.HabitMatchers.containsHabit; +import static org.isoron.uhabits.HabitMatchers.withName; + +public class MainActivityActions +{ + public static String addHabit() + { + return addHabit(false); + } + + public static String addHabit(boolean openDialogs) + { + String name = "New Habit " + new Random().nextInt(1000000); + String description = "Did you perform your new habit today?"; + String num = "4"; + String den = "8"; + + onView(withId(R.id.action_add)) + .perform(click()); + + typeHabitData(name, description, num, den); + + if(openDialogs) + { + onView(withId(R.id.buttonPickColor)) + .perform(click()); + pressBack(); + onView(withId(R.id.inputReminderTime)) + .perform(click()); + onView(withText("Done")) + .perform(click()); + onView(withId(R.id.inputReminderDays)) + .perform(click()); + onView(withText("OK")) + .perform(click()); + } + + onView(withId(R.id.buttonSave)) + .perform(click()); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)); + + return name; + } + + public static void typeHabitData(String name, String description, String num, String den) + { + onView(withId(R.id.input_name)) + .perform(replaceText(name)); + onView(withId(R.id.input_description)) + .perform(replaceText(description)); + onView(withId(R.id.input_freq_num)) + .perform(replaceText(num)); + onView(withId(R.id.input_freq_den)) + .perform(replaceText(den)); + } + + public static void selectHabit(String name) + { + selectHabits(Collections.singletonList(name)); + } + + public static void selectHabits(List names) + { + boolean first = true; + for(String name : names) + { + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(first ? longClick() : click()); + + first = false; + } + } + + public static void assertHabitsDontExist(List names) + { + for(String name : names) + onView(withId(R.id.listView)) + .check(matches(not(containsHabit(withName(name))))); + } + + public static void assertHabitExists(String name) + { + List names = new LinkedList<>(); + names.add(name); + assertHabitsExist(names); + } + + public static void assertHabitsExist(List names) + { + for(String name : names) + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .check(matches(isDisplayed())); + } + + public static void deleteHabit(String name) + { + deleteHabits(Collections.singletonList(name)); + } + + public static void deleteHabits(List names) + { + selectHabits(names); + clickActionModeMenuItem(R.string.delete); + onView(withText("OK")) + .perform(click()); + assertHabitsDontExist(names); + } + + public static void clickActionModeMenuItem(int stringId) + { + try + { + onView(withText(stringId)).perform(click()); + } + catch (Exception e1) + { + try + { + onView(withContentDescription(stringId)).perform(click()); + } + catch(Exception e2) + { + openContextualActionModeOverflowMenu(); + onView(withText(stringId)).perform(click()); + } + } + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java new file mode 100644 index 000000000..b6047c69a --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/MainTest.java @@ -0,0 +1,200 @@ +package org.isoron.uhabits; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.intent.rule.IntentsTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.LargeTest; + +import org.isoron.uhabits.models.Habit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import static android.support.test.espresso.Espresso.onData; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static android.support.test.espresso.Espresso.pressBack; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.longClick; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.action.ViewActions.swipeLeft; +import static android.support.test.espresso.action.ViewActions.swipeRight; +import static android.support.test.espresso.action.ViewActions.swipeUp; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isRoot; +import static android.support.test.espresso.matcher.ViewMatchers.withClassName; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.isoron.uhabits.HabitMatchers.withName; +import static org.isoron.uhabits.HabitViewActions.clickAtRandomLocations; +import static org.isoron.uhabits.HabitViewActions.toggleAllCheckmarks; +import static org.isoron.uhabits.MainActivityActions.addHabit; +import static org.isoron.uhabits.MainActivityActions.assertHabitExists; +import static org.isoron.uhabits.MainActivityActions.assertHabitsDontExist; +import static org.isoron.uhabits.MainActivityActions.assertHabitsExist; +import static org.isoron.uhabits.MainActivityActions.clickActionModeMenuItem; +import static org.isoron.uhabits.MainActivityActions.deleteHabit; +import static org.isoron.uhabits.MainActivityActions.deleteHabits; +import static org.isoron.uhabits.MainActivityActions.selectHabit; +import static org.isoron.uhabits.MainActivityActions.selectHabits; +import static org.isoron.uhabits.MainActivityActions.typeHabitData; +import static org.isoron.uhabits.ShowHabitActivityActions.openHistoryEditor; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class MainTest +{ + @Rule + public IntentsTestRule activityRule = new IntentsTestRule<>( + MainActivity.class); + + @Before + public void skipTutorial() + { + try + { + for (int i = 0; i < 10; i++) + onView(allOf(withClassName(endsWith("AppCompatImageButton")), + isDisplayed())).perform(click()); + } + catch (NoMatchingViewException e) + { + // ignored + } + } + + @Test + public void testArchiveHabits() + { + List names = new LinkedList<>(); + Context context = InstrumentationRegistry.getTargetContext(); + + for(int i = 0; i < 3; i++) + names.add(addHabit()); + + selectHabits(names); + + clickActionModeMenuItem(R.string.archive); + assertHabitsDontExist(names); + + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.show_archived)) + .perform(click()); + + assertHabitsExist(names); + selectHabits(names); + clickActionModeMenuItem(R.string.unarchive); + + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.show_archived)) + .perform(click()); + + assertHabitsExist(names); + deleteHabits(names); + } + + @Test + public void testAddInvalidHabit() + { + onView(withId(R.id.action_add)) + .perform(click()); + + typeHabitData("", "", "15", "7"); + + onView(withId(R.id.buttonSave)).perform(click()); + onView(withId(R.id.input_name)).check(matches(isDisplayed())); + } + + @Test + public void testAddHabitAndViewStats() + { + String name = addHabit(true); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.llButtons)) + .perform(toggleAllCheckmarks()); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); + + onView(withId(R.id.scoreView)) + .perform(swipeRight()); + + onView(withId(R.id.punchcardView)) + .perform(scrollTo()); + } + + @Test + public void testEditHabit() + { + String name = addHabit(); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(longClick()); + + clickActionModeMenuItem(R.string.edit); + + String modifiedName = "Modified " + new Random().nextInt(10000); + typeHabitData(modifiedName, "", "1", "1"); + + onView(withId(R.id.buttonSave)) + .perform(click()); + + assertHabitExists(modifiedName); + + selectHabit(modifiedName); + clickActionModeMenuItem(R.string.color_picker_default_title); + pressBack(); + + deleteHabit(modifiedName); + } + + @Test + public void testEditHistory() + { + String name = addHabit(); + + onData(allOf(is(instanceOf(Habit.class)), withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); + + openHistoryEditor(); + onView(withClassName(endsWith("HabitHistoryView"))) + .perform(clickAtRandomLocations(20)); + + pressBack(); + onView(withId(R.id.historyView)) + .perform(scrollTo(), swipeRight(), swipeLeft()); + } + + @Test + public void testSettings() + { + Context context = InstrumentationRegistry.getContext(); + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.settings)).perform(click()); + } + + @Test + public void testAbout() + { + Context context = InstrumentationRegistry.getContext(); + openActionBarOverflowOrOptionsMenu(context); + onView(withText(R.string.about)).perform(click()); + onView(isRoot()).perform(swipeUp()); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java new file mode 100644 index 000000000..cacc9f21f --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Á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; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.scrollTo; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +public class ShowHabitActivityActions +{ + public static void openHistoryEditor() + { + onView(withId(R.id.btEditHistory)) + .perform(scrollTo(), click()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c224ffa93..545c02988 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,20 +1,38 @@ + + + android:versionCode="10" + android:versionName="1.3.0"> - + + android:maxSdkVersion="18"/> + android:maxSdkVersion="18"/> - - @@ -39,16 +55,13 @@ - + + - - + + + + + + + + + + android:label="@string/checkmark"> + @@ -85,10 +113,11 @@ + android:label="@string/history"> + @@ -96,10 +125,11 @@ + android:label="@string/habit_strength"> + @@ -107,24 +137,29 @@ + android:label="@string/streaks"> + - - + - + - + + + + diff --git a/app/src/main/java/org/isoron/helpers/ColorHelper.java b/app/src/main/java/org/isoron/helpers/ColorHelper.java index 2a4a0a258..de49d654d 100644 --- a/app/src/main/java/org/isoron/helpers/ColorHelper.java +++ b/app/src/main/java/org/isoron/helpers/ColorHelper.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.helpers; diff --git a/app/src/main/java/org/isoron/helpers/Command.java b/app/src/main/java/org/isoron/helpers/Command.java index 913292d11..7472b096f 100644 --- a/app/src/main/java/org/isoron/helpers/Command.java +++ b/app/src/main/java/org/isoron/helpers/Command.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.helpers; diff --git a/app/src/main/java/org/isoron/helpers/DateHelper.java b/app/src/main/java/org/isoron/helpers/DateHelper.java index 45b9144cf..0aef8c0c6 100644 --- a/app/src/main/java/org/isoron/helpers/DateHelper.java +++ b/app/src/main/java/org/isoron/helpers/DateHelper.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.helpers; diff --git a/app/src/main/java/org/isoron/helpers/DialogHelper.java b/app/src/main/java/org/isoron/helpers/DialogHelper.java index 48d2cad02..e65a50430 100644 --- a/app/src/main/java/org/isoron/helpers/DialogHelper.java +++ b/app/src/main/java/org/isoron/helpers/DialogHelper.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.helpers; diff --git a/app/src/main/java/org/isoron/helpers/ReplayableActivity.java b/app/src/main/java/org/isoron/helpers/ReplayableActivity.java index 11f2ee34b..d2fcdc350 100644 --- a/app/src/main/java/org/isoron/helpers/ReplayableActivity.java +++ b/app/src/main/java/org/isoron/helpers/ReplayableActivity.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.helpers; diff --git a/app/src/main/java/org/isoron/uhabits/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/AboutActivity.java new file mode 100644 index 000000000..20af24efe --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/AboutActivity.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 Á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; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import org.isoron.helpers.ColorHelper; + +public class AboutActivity extends Activity implements View.OnClickListener +{ + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.about); + + if (android.os.Build.VERSION.SDK_INT >= 21) + { + int color = getResources().getColor(R.color.blue_700); + int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f); + getActionBar().setBackgroundDrawable(new ColorDrawable(color)); + getWindow().setStatusBarColor(darkerColor); + } + + TextView tvVersion = (TextView) findViewById(R.id.tvVersion); + TextView tvRate = (TextView) findViewById(R.id.tvRate); + TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback); + TextView tvSource = (TextView) findViewById(R.id.tvSource); + + tvVersion.setText(String.format(getResources().getString(R.string.version_n), + BuildConfig.VERSION_NAME)); + tvRate.setOnClickListener(this); + tvFeedback.setOnClickListener(this); + tvSource.setOnClickListener(this); + } + + @Override + public void onClick(View v) + { + switch (v.getId()) + { + case R.id.tvRate: + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse("market://details?id=org.isoron.uhabits")); + startActivity(intent); + break; + } + + case R.id.tvFeedback: + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SENDTO); + intent.setData(Uri.parse("mailto:isoron+habits@gmail.com?" + + "subject=Feedback%20about%20Loop%20Habit%20Tracker")); + startActivity(intent); + break; + } + + case R.id.tvSource: + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse("https://github.com/iSoron/uhabits")); + startActivity(intent); + break; + } + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index 3942f8e52..a43757c84 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; @@ -104,6 +107,11 @@ private void checkHabit(Context context, Intent intent) habit.save(); dismissNotification(context, habit); + sendRefreshBroadcast(context); + } + + public static void sendRefreshBroadcast(Context context) + { LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context); Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH); manager.sendBroadcast(refreshIntent); diff --git a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java index 7243d6f61..6baa562f2 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; diff --git a/app/src/main/java/org/isoron/uhabits/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/IntroActivity.java index e802e2248..e7685aa87 100644 --- a/app/src/main/java/org/isoron/uhabits/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/IntroActivity.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index e9f07bd01..256761aba 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; @@ -38,6 +41,7 @@ import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.widgets.CheckmarkWidgetProvider; +import org.isoron.uhabits.widgets.FrequencyWidgetProvider; import org.isoron.uhabits.widgets.HistoryWidgetProvider; import org.isoron.uhabits.widgets.ScoreWidgetProvider; import org.isoron.uhabits.widgets.StreakWidgetProvider; @@ -117,9 +121,18 @@ public boolean onOptionsItemSelected(MenuItem item) switch (item.getItemId()) { case R.id.action_settings: + { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); return true; + } + + case R.id.action_about: + { + Intent intent = new Intent(this, AboutActivity.class); + startActivity(intent); + return true; + } default: return super.onOptionsItemSelected(item); @@ -138,7 +151,16 @@ public void onHabitClicked(Habit habit) public void onPostExecuteCommand(Long refreshKey) { listHabitsFragment.onPostExecuteCommand(refreshKey); - updateWidgets(this); + + new AsyncTask() + { + @Override + protected Void doInBackground(Void... params) + { + updateWidgets(MainActivity.this); + return null; + } + }; } public static void updateWidgets(Context context) @@ -147,6 +169,7 @@ public static void updateWidgets(Context context) updateWidgets(context, HistoryWidgetProvider.class); updateWidgets(context, ScoreWidgetProvider.class); updateWidgets(context, StreakWidgetProvider.class); + updateWidgets(context, FrequencyWidgetProvider.class); } private static void updateWidgets(Context context, Class providerClass) diff --git a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/SettingsActivity.java index a39e8ffaa..9e43b1bba 100644 --- a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/SettingsActivity.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; diff --git a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java index 3eba606ba..041685e6e 100644 --- a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java @@ -1,36 +1,47 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits; +import android.app.ActionBar; +import android.content.BroadcastReceiver; import android.content.ContentUris; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; +import android.support.v4.content.LocalBroadcastManager; import org.isoron.helpers.ReplayableActivity; +import org.isoron.uhabits.fragments.ShowHabitFragment; import org.isoron.uhabits.models.Habit; public class ShowHabitActivity extends ReplayableActivity { public Habit habit; + private Receiver receiver; + private LocalBroadcastManager localBroadcastManager; + + private ShowHabitFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) @@ -39,34 +50,39 @@ protected void onCreate(Bundle savedInstanceState) Uri data = getIntent().getData(); habit = Habit.get(ContentUris.parseId(data)); - getActionBar().setTitle(habit.name); + ActionBar actionBar = getActionBar(); - if (android.os.Build.VERSION.SDK_INT >= 21) + if(actionBar != null) { - getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color)); + actionBar.setTitle(habit.name); + + if (android.os.Build.VERSION.SDK_INT >= 21) + actionBar.setBackgroundDrawable(new ColorDrawable(habit.color)); } setContentView(R.layout.show_habit_activity); - } - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - getMenuInflater().inflate(R.menu.show_habit_activity_menu, menu); - return true; + fragment = (ShowHabitFragment) getFragmentManager().findFragmentById(R.id.fragment2); + + receiver = new Receiver(); + localBroadcastManager = LocalBroadcastManager.getInstance(this); + localBroadcastManager.registerReceiver(receiver, + new IntentFilter(MainActivity.ACTION_REFRESH)); } - @Override - public boolean onOptionsItemSelected(MenuItem item) + class Receiver extends BroadcastReceiver { - switch (item.getItemId()) + @Override + public void onReceive(Context context, Intent intent) { - case R.id.action_settings: - Intent intent = new Intent(this, SettingsActivity.class); - startActivity(intent); - return true; + fragment.refreshData(); } + } - return super.onOptionsItemSelected(item); + @Override + protected void onDestroy() + { + localBroadcastManager.unregisterReceiver(receiver); + super.onDestroy(); } } diff --git a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java index 0d1ae561f..3aee23dc1 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; diff --git a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java index 547024b42..f9b00ba89 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; diff --git a/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java index aa758cbb7..e3fba3e35 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; diff --git a/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java index 6dff1f59d..b1c2ee217 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; @@ -36,12 +39,14 @@ public void execute() { for(Habit h : habits) h.cascadeDelete(); + + Habit.rebuildOrder(); } @Override public void undo() { - + throw new UnsupportedOperationException(); } public Integer getExecuteStringId() diff --git a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java index 53b4bbf1b..ccd641c8b 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; @@ -33,8 +36,8 @@ public EditHabitCommand(Habit original, Habit modified) this.modified = new Habit(modified); this.original = new Habit(original); - hasIntervalChanged = (this.original.freqDen != this.modified.freqDen || - this.original.freqNum != this.modified.freqNum); + hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) || + !this.original.freqNum.equals(this.modified.freqNum)); } public void execute() diff --git a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java index c19ff5a5d..fe573ddaf 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; diff --git a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java index 258627321..08d34af69 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.commands; diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java new file mode 100644 index 000000000..47a23cf28 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2016 Á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.dialogs; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; + +import com.android.colorpicker.ColorPickerDialog; +import com.android.colorpicker.ColorPickerSwatch; + +import org.isoron.helpers.ColorHelper; +import org.isoron.helpers.DialogHelper; +import org.isoron.helpers.ReplayableActivity; +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.ArchiveHabitsCommand; +import org.isoron.uhabits.commands.ChangeHabitColorCommand; +import org.isoron.uhabits.commands.DeleteHabitsCommand; +import org.isoron.uhabits.commands.UnarchiveHabitsCommand; +import org.isoron.uhabits.fragments.EditHabitFragment; +import org.isoron.uhabits.io.CSVExporter; +import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.models.Habit; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +public class HabitSelectionCallback implements ActionMode.Callback +{ + private HabitListLoader loader; + private List selectedPositions; + private ReplayableActivity activity; + private Listener listener; + private DialogHelper.OnSavedListener onSavedListener; + private ProgressBar progressBar; + + public interface Listener + { + void onActionModeDestroyed(ActionMode mode); + } + + public HabitSelectionCallback(ReplayableActivity activity, HabitListLoader loader) + { + this.activity = activity; + this.loader = loader; + selectedPositions = new LinkedList<>(); + } + + public void setListener(Listener listener) + { + this.listener = listener; + } + + public void setProgressBar(ProgressBar progressBar) + { + this.progressBar = progressBar; + } + + public void setOnSavedListener(DialogHelper.OnSavedListener onSavedListener) + { + this.onSavedListener = onSavedListener; + } + + public void setSelectedPositions(List selectedPositions) + { + this.selectedPositions = selectedPositions; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + activity.getMenuInflater().inflate(R.menu.list_habits_context, menu); + updateTitle(mode); + updateActions(menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) + { + updateTitle(mode); + updateActions(menu); + return true; + } + + private void updateActions(Menu menu) + { + boolean showEdit = (selectedPositions.size() == 1); + boolean showArchive = true; + boolean showUnarchive = true; + for (int i : selectedPositions) + { + Habit h = loader.habitsList.get(i); + if (h.isArchived()) + { + showArchive = false; + } + else showUnarchive = false; + } + + MenuItem itemEdit = menu.findItem(R.id.action_edit_habit); + MenuItem itemColor = menu.findItem(R.id.action_color); + MenuItem itemArchive = menu.findItem(R.id.action_archive_habit); + MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit); + + itemColor.setVisible(true); + itemEdit.setVisible(showEdit); + itemArchive.setVisible(showArchive); + itemUnarchive.setVisible(showUnarchive); + } + + private void updateTitle(ActionMode mode) + { + mode.setTitle("" + selectedPositions.size()); + } + + @Override + public boolean onActionItemClicked(final ActionMode mode, MenuItem item) + { + final LinkedList selectedHabits = new LinkedList<>(); + for (int i : selectedPositions) + selectedHabits.add(loader.habitsList.get(i)); + + Habit firstHabit = selectedHabits.getFirst(); + + switch (item.getItemId()) + { + case R.id.action_archive_habit: + activity.executeCommand(new ArchiveHabitsCommand(selectedHabits), null); + mode.finish(); + return true; + + case R.id.action_unarchive_habit: + activity.executeCommand(new UnarchiveHabitsCommand(selectedHabits), null); + mode.finish(); + return true; + + case R.id.action_edit_habit: + { + EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId()); + frag.setOnSavedListener(onSavedListener); + frag.show(activity.getFragmentManager(), "editHabit"); + return true; + } + + case R.id.action_color: + { + ColorPickerDialog picker = ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorHelper.palette, firstHabit.color, 4, ColorPickerDialog.SIZE_SMALL); + + picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() + { + public void onColorSelected(int color) + { + activity.executeCommand( + new ChangeHabitColorCommand(selectedHabits, color), null); + mode.finish(); + } + }); + picker.show(activity.getFragmentManager(), "picker"); + return true; + } + + case R.id.action_delete: + { + new AlertDialog.Builder(activity).setTitle(R.string.delete_habits) + .setMessage(R.string.delete_habits_message) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + activity.executeCommand( + new DeleteHabitsCommand(selectedHabits), null); + mode.finish(); + } + }).setNegativeButton(android.R.string.no, null) + .show(); + + return true; + } + + case R.id.action_export_csv: + { + onExportHabitsClick(selectedHabits); + return true; + } + } + + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) + { + if(listener != null) listener.onActionModeDestroyed(mode); + } + + private void onExportHabitsClick(final LinkedList selectedHabits) + { + new AsyncTask() + { + String filename; + + @Override + protected void onPreExecute() + { + if(progressBar != null) + { + progressBar.setIndeterminate(true); + progressBar.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onPostExecute(Void aVoid) + { + if(filename != null) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); + + activity.startActivity(intent); + } + + if(progressBar != null) + progressBar.setVisibility(View.GONE); + } + + @Override + protected Void doInBackground(Void... params) + { + CSVExporter exporter = new CSVExporter(activity, selectedHabits); + filename = exporter.writeArchive(); + return null; + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HintManager.java b/app/src/main/java/org/isoron/uhabits/dialogs/HintManager.java new file mode 100644 index 000000000..0a3d4dead --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HintManager.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Á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.dialogs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; +import android.widget.TextView; + +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; + +public class HintManager +{ + private Context context; + private SharedPreferences prefs; + private View hintView; + + public HintManager(Context context, View hintView) + { + this.context = context; + this.hintView = hintView; + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public void dismissHint() + { + hintView.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter() + { + @Override + public void onAnimationEnd(Animator animation) + { + hintView.setVisibility(View.GONE); + } + }); + } + + public void showHintIfAppropriate() + { + Integer lastHintNumber = prefs.getInt("last_hint_number", -1); + Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1); + + if (DateHelper.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1); + } + + private void showHint(int hintNumber) + { + String[] hints = context.getResources().getStringArray(R.array.hints); + if (hintNumber >= hints.length) return; + + prefs.edit().putInt("last_hint_number", hintNumber).apply(); + prefs.edit().putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply(); + + TextView tvContent = (TextView) hintView.findViewById(R.id.hintContent); + tvContent.setText(hints[hintNumber]); + + hintView.setAlpha(0.0f); + hintView.setVisibility(View.VISIBLE); + hintView.animate().alpha(1f).setDuration(500); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java new file mode 100644 index 000000000..59d4c1645 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 Á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.dialogs; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitHistoryView; + +public class HistoryEditorDialog extends DialogFragment + implements DialogInterface.OnClickListener +{ + private Habit habit; + private Listener listener; + HabitHistoryView historyView; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Context context = getActivity(); + historyView = new HabitHistoryView(context, null); + int p = (int) getResources().getDimension(R.dimen.history_editor_padding); + + if(savedInstanceState != null) + { + long id = savedInstanceState.getLong("habit", -1); + if(id > 0) this.habit = Habit.get(id); + } + + historyView.setPadding(p, 0, p, 0); + historyView.setHabit(habit); + historyView.setIsEditable(true); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.history) + .setView(historyView) + .setPositiveButton(android.R.string.ok, this); + + return builder.create(); + } + + @Override + public void onResume() + { + super.onResume(); + + DisplayMetrics metrics = getResources().getDisplayMetrics(); + int maxHeight = getResources().getDimensionPixelSize(R.dimen.history_editor_max_height); + int width = metrics.widthPixels; + int height = Math.min(metrics.heightPixels, maxHeight); + + Log.d("HistoryEditorDialog", String.format("h=%d max_h=%d", height, maxHeight)); + + getDialog().getWindow().setLayout(width, height); + } + + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + + public void setHabit(Habit habit) + { + this.habit = habit; + if(historyView != null) historyView.setHabit(habit); + } + + @Override + public void onPause() + { + super.onPause(); + if(listener != null) listener.onHistoryEditorClosed(); + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + outState.putLong("habit", habit.getId()); + } + + public void setListener(Listener listener) + { + this.listener = listener; + } + + public interface Listener { + void onHistoryEditorClosed(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java index be68359b9..300dcd3ea 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2016 Á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.dialogs; import android.app.AlertDialog; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java index 82a32d37d..a48087023 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.fragments; @@ -57,13 +60,20 @@ public class EditHabitFragment extends DialogFragment private OnSavedListener onSavedListener; - private Habit originalHabit, modifiedHabit; - private TextView tvName, tvDescription, tvFreqNum, tvFreqDen, tvReminderTime, tvReminderDays; + private Habit originalHabit; + private Habit modifiedHabit; + + private TextView tvName; + private TextView tvDescription; + private TextView tvFreqNum; + private TextView tvFreqDen; + private TextView tvReminderTime; + private TextView tvReminderDays; private SharedPreferences prefs; private boolean is24HourMode; - static EditHabitFragment editSingleHabitFragment(long id) + public static EditHabitFragment editSingleHabitFragment(long id) { EditHabitFragment frag = new EditHabitFragment(); Bundle args = new Bundle(); @@ -73,7 +83,7 @@ static EditHabitFragment editSingleHabitFragment(long id) return frag; } - static EditHabitFragment createHabitFragment() + public static EditHabitFragment createHabitFragment() { EditHabitFragment frag = new EditHabitFragment(); Bundle args = new Bundle(); @@ -96,13 +106,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Button buttonSave = (Button) view.findViewById(R.id.buttonSave); Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard); + ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor); buttonSave.setOnClickListener(this); buttonDiscard.setOnClickListener(this); tvReminderTime.setOnClickListener(this); tvReminderDays.setOnClickListener(this); - - ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor); + buttonPickColor.setOnClickListener(this); prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); @@ -134,14 +144,27 @@ else if (mode == EDIT_MODE) tvDescription.append(modifiedHabit.description); } + if(savedInstanceState != null) + { + modifiedHabit.color = savedInstanceState.getInt("color", modifiedHabit.color); + modifiedHabit.reminderMin = savedInstanceState.getInt("reminderMin", -1); + modifiedHabit.reminderHour = savedInstanceState.getInt("reminderHour", -1); + modifiedHabit.reminderDays = savedInstanceState.getInt("reminderDays", -1); + + if(modifiedHabit.reminderMin < 0) + { + modifiedHabit.reminderMin = null; + modifiedHabit.reminderHour = null; + modifiedHabit.reminderDays = 127; + } + } + tvFreqNum.append(modifiedHabit.freqNum.toString()); tvFreqDen.append(modifiedHabit.freqDen.toString()); changeColor(modifiedHabit.color); updateReminder(); - buttonPickColor.setOnClickListener(this); - return view; } @@ -225,8 +248,6 @@ public void onColorSelected(int color) private void onSaveButtonClick() { - Command command = null; - modifiedHabit.name = tvName.getText().toString().trim(); modifiedHabit.description = tvDescription.getText().toString().trim(); modifiedHabit.freqNum = Integer.parseInt(tvFreqNum.getText().toString()); @@ -239,6 +260,7 @@ private void onSaveButtonClick() editor.putInt("pref_default_habit_freq_den", modifiedHabit.freqDen); editor.apply(); + Command command = null; Habit savedHabit = null; if (mode == EDIT_MODE) @@ -246,8 +268,10 @@ private void onSaveButtonClick() command = new EditHabitCommand(originalHabit, modifiedHabit); savedHabit = originalHabit; } - - if (mode == CREATE_MODE) command = new CreateHabitCommand(modifiedHabit); + else if (mode == CREATE_MODE) + { + command = new CreateHabitCommand(modifiedHabit); + } if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit); @@ -325,9 +349,24 @@ public void onWeekdaysPicked(boolean[] selectedDays) int count = 0; for(int i = 0; i < 7; i++) if(selectedDays[i]) count++; + if(count == 0) Arrays.fill(selectedDays, true); modifiedHabit.reminderDays = DateHelper.packWeekdayList(selectedDays); updateReminder(); } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + outState.putInt("color", modifiedHabit.color); + if(modifiedHabit.reminderHour != null) + { + outState.putInt("reminderMin", modifiedHabit.reminderMin); + outState.putInt("reminderHour", modifiedHabit.reminderHour); + outState.putInt("reminderDays", modifiedHabit.reminderDays); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java new file mode 100644 index 000000000..3ef39713e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 Á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.fragments; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; +import org.isoron.uhabits.helpers.ListHabitsHelper; +import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.models.Habit; + +import java.util.List; + +class HabitListAdapter extends BaseAdapter +{ + private LayoutInflater inflater; + private HabitListLoader loader; + private ListHabitsHelper helper; + private List selectedPositions; + private View.OnLongClickListener onCheckmarkLongClickListener; + private View.OnClickListener onCheckmarkClickListener; + + public HabitListAdapter(Context context, HabitListLoader loader) + { + this.loader = loader; + + inflater = LayoutInflater.from(context); + helper = new ListHabitsHelper(context, loader); + } + + @Override + public int getCount() + { + return loader.habits.size(); + } + + @Override + public Habit getItem(int position) + { + return loader.habitsList.get(position); + } + + @Override + public long getItemId(int position) + { + return (getItem(position)).getId(); + } + + @Override + public View getView(int position, View view, ViewGroup parent) + { + final Habit habit = loader.habitsList.get(position); + + if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateHelper.getStartOfToday()) + { + view = inflater.inflate(R.layout.list_habits_item, null); + helper.initializeLabelAndIcon(view); + helper.inflateCheckmarkButtons(view, onCheckmarkLongClickListener, + onCheckmarkClickListener, inflater); + } + + TextView tvStar = ((TextView) view.findViewById(R.id.tvStar)); + TextView tvName = (TextView) view.findViewById(R.id.label); + LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); + LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons); + + llInner.setTag(R.string.habit_key, habit.getId()); + + helper.updateNameAndIcon(habit, tvStar, tvName); + helper.updateCheckmarkButtons(habit, llButtons); + + boolean selected = selectedPositions.contains(position); + helper.updateHabitBackground(llInner, selected); + + return view; + } + + public void setSelectedPositions(List selectedPositions) + { + this.selectedPositions = selectedPositions; + } + + public void setOnCheckmarkLongClickListener(View.OnLongClickListener listener) + { + this.onCheckmarkLongClickListener = listener; + } + + public void setOnCheckmarkClickListener(View.OnClickListener listener) + { + this.onCheckmarkClickListener = listener; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 278e54345..ef11a2301 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -1,37 +1,29 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.fragments; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.app.Activity; -import android.app.AlertDialog; import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; -import android.util.DisplayMetrics; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -46,293 +38,107 @@ import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; -import android.widget.BaseAdapter; -import android.widget.Button; import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView.DropListener; -import org.isoron.helpers.ColorHelper; import org.isoron.helpers.Command; import org.isoron.helpers.DateHelper; import org.isoron.helpers.DialogHelper; import org.isoron.helpers.DialogHelper.OnSavedListener; import org.isoron.helpers.ReplayableActivity; import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.ArchiveHabitsCommand; -import org.isoron.uhabits.commands.ChangeHabitColorCommand; -import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.commands.UnarchiveHabitsCommand; +import org.isoron.uhabits.dialogs.HabitSelectionCallback; +import org.isoron.uhabits.dialogs.HintManager; +import org.isoron.uhabits.helpers.ListHabitsHelper; import org.isoron.uhabits.helpers.ReminderHelper; -import org.isoron.uhabits.io.CSVExporter; import org.isoron.uhabits.loaders.HabitListLoader; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import java.io.File; import java.util.Date; -import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; public class ListHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, - OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener + OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener, + HabitSelectionCallback.Listener { - private class ListHabitsActionBarCallback implements ActionMode.Callback - { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu); - updateTitle(mode); - updateActions(menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) - { - updateTitle(mode); - updateActions(menu); - return true; - } - - private void updateActions(Menu menu) - { - boolean showEdit = (selectedPositions.size() == 1); - boolean showColor = true; - boolean showArchive = true; - boolean showUnarchive = true; - - if(showEdit) showColor = false; - for(int i : selectedPositions) - { - Habit h = loader.habitsList.get(i); - if(h.isArchived()) - { - showColor = false; - showArchive = false; - } - else showUnarchive = false; - } - - MenuItem itemEdit = menu.findItem(R.id.action_edit_habit); - MenuItem itemColor = menu.findItem(R.id.action_color); - MenuItem itemArchive = menu.findItem(R.id.action_archive_habit); - MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit); - - itemEdit.setVisible(showEdit); - itemColor.setVisible(showColor); - itemArchive.setVisible(showArchive); - itemUnarchive.setVisible(showUnarchive); - } - - private void updateTitle(ActionMode mode) - { - mode.setTitle("" + selectedPositions.size()); - } - - @Override - public boolean onActionItemClicked(final ActionMode mode, MenuItem item) - { - final LinkedList selectedHabits = new LinkedList<>(); - for(int i : selectedPositions) - selectedHabits.add(loader.habitsList.get(i)); - - Habit firstHabit = selectedHabits.getFirst(); - - switch(item.getItemId()) - { - case R.id.action_archive_habit: - executeCommand(new ArchiveHabitsCommand(selectedHabits), null); - mode.finish(); - return true; - - case R.id.action_unarchive_habit: - executeCommand(new UnarchiveHabitsCommand(selectedHabits), null); - mode.finish(); - return true; - - case R.id.action_edit_habit: - { - EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId()); - frag.setOnSavedListener(ListHabitsFragment.this); - frag.show(getFragmentManager(), "dialog"); - return true; - } - - case R.id.action_color: - { - ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorHelper.palette, - firstHabit.color, 4, ColorPickerDialog.SIZE_SMALL); - - picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() - { - public void onColorSelected(int color) - { - executeCommand(new ChangeHabitColorCommand(selectedHabits, color), null); - mode.finish(); - } - }); - picker.show(getFragmentManager(), "picker"); - return true; - } - - case R.id.action_delete: - { - new AlertDialog.Builder(activity) - .setTitle(R.string.delete_habits) - .setMessage(R.string.delete_habits_message) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - executeCommand(new DeleteHabitsCommand(selectedHabits), null); - mode.finish(); - } - }).setNegativeButton(android.R.string.no, null) - .show(); - - return true; - } - - case R.id.action_export_csv: - { - onExportHabitsClick(selectedHabits); - return true; - } - } - - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) - { - actionMode = null; - - selectedPositions.clear(); - adapter.notifyDataSetChanged(); - - listView.setDragEnabled(true); - } - } - - public static final int INACTIVE_COLOR = Color.rgb(200, 200, 200); - public static final int INACTIVE_CHECKMARK_COLOR = Color.rgb(230, 230, 230); - - public static final int HINT_INTERVAL = 5; - public static final int HINT_INTERVAL_OFFSET = 2; - - public interface OnHabitClickListener - { - void onHabitClicked(Habit habit); - } - - ListHabitsAdapter adapter; - DragSortListView listView; - ReplayableActivity activity; - TextView tvNameHeader; long lastLongClick = 0; - - private int tvNameWidth; - private int buttonCount; - private View llEmpty; - private View llHint; - - private OnHabitClickListener habitClickListener; private boolean isShortToggleEnabled; - - private HabitListLoader loader; private boolean showArchived; - private SharedPreferences prefs; private ActionMode actionMode; + private HabitListAdapter adapter; + private HabitListLoader loader; + private HintManager hintManager; + private ListHabitsHelper helper; private List selectedPositions; - private DragSortController dragSortController; + private OnHabitClickListener habitClickListener; + private ReplayableActivity activity; + private SharedPreferences prefs; + + private DragSortListView listView; + private LinearLayout llButtonsHeader; private ProgressBar progressBar; + private View llEmpty; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - DisplayMetrics dm = getResources().getDisplayMetrics(); - int width = (int) (dm.widthPixels / dm.density); - buttonCount = Math.max(0, (int) ((width - 160) / 42.0)); - tvNameWidth = (int) ((width - 30 - buttonCount * 42) * dm.density); + View view = inflater.inflate(R.layout.list_habits_fragment, container, false); + View llHint = view.findViewById(R.id.llHint); + TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); + listView = (DragSortListView) view.findViewById(R.id.listView); + llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); + llEmpty = view.findViewById(R.id.llEmpty); + progressBar = (ProgressBar) view.findViewById(R.id.progressBar); + selectedPositions = new LinkedList<>(); loader = new HabitListLoader(); + helper = new ListHabitsHelper(activity, loader); + hintManager = new HintManager(activity, llHint); + loader.setListener(this); - loader.setCheckmarkCount(buttonCount); + loader.setCheckmarkCount(helper.getButtonCount()); + loader.setProgressBar(progressBar); - View view = inflater.inflate(R.layout.list_habits_fragment, container, false); - tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader); + llHint.setOnClickListener(this); + tvStarEmpty.setTypeface(helper.getFontawesome()); - progressBar = (ProgressBar) view.findViewById(R.id.progressBar); - loader.setProgressBar(progressBar); + adapter = new HabitListAdapter(getActivity(), loader); + adapter.setSelectedPositions(selectedPositions); + adapter.setOnCheckmarkClickListener(this); + adapter.setOnCheckmarkLongClickListener(this); + + DragSortListView.DragListener dragListener = new HabitsDragListener(); + DragSortController dragSortController = new HabitsDragSortController(); - adapter = new ListHabitsAdapter(getActivity()); - listView = (DragSortListView) view.findViewById(R.id.listView); listView.setAdapter(adapter); listView.setOnItemClickListener(this); listView.setOnItemLongClickListener(this); listView.setDropListener(this); - listView.setDragListener(new DragSortListView.DragListener() - { - @Override - public void drag(int from, int to) - { - } - - @Override - public void startDrag(int position) - { - selectItem(position); - } - }); - - dragSortController = new DragSortController(listView) { - @Override - public View onCreateFloatView(int position) - { - return adapter.getView(position, null, null); - } - - @Override - public void onDestroyFloatView(View floatView) - { - } - }; - dragSortController.setRemoveEnabled(false); - + listView.setDragListener(dragListener); listView.setFloatViewManager(dragSortController); listView.setDragEnabled(true); listView.setLongClickable(true); - llHint = view.findViewById(R.id.llHint); - llHint.setOnClickListener(this); - - Typeface fontawesome = Typeface.createFromAsset(getActivity().getAssets(), - "fontawesome-webfont.ttf"); - ((TextView) view.findViewById(R.id.tvStarEmpty)).setTypeface(fontawesome); - llEmpty = view.findViewById(R.id.llEmpty); + if(savedInstanceState != null) + { + EditHabitFragment frag = (EditHabitFragment) getFragmentManager() + .findFragmentByTag("editHabit"); + if(frag != null) frag.setOnSavedListener(this); + } loader.updateAllHabits(true); - setHasOptionsMenu(true); - - selectedPositions = new LinkedList<>(); + setHasOptionsMenu(true); return view; } @@ -356,42 +162,19 @@ public void onResume() if (timestamp != null && timestamp != DateHelper.getStartOfToday()) loader.updateAllHabits(true); - updateEmptyMessage(); - updateHeader(); - showNextHint(); + helper.updateEmptyMessage(llEmpty); + helper.updateHeader(llButtonsHeader); + hintManager.showHintIfAppropriate(); adapter.notifyDataSetChanged(); isShortToggleEnabled = prefs.getBoolean("pref_short_toggle", false); } - private void updateHeader() - { - LayoutInflater inflater = activity.getLayoutInflater(); - View view = getView(); - - if (view == null) return; - - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); - - LinearLayout llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); - llButtonsHeader.removeAllViews(); - - for (int i = 0; i < buttonCount; i++) - { - View tvDay = inflater.inflate(R.layout.list_habits_header_check, null); - Button btCheck = (Button) tvDay.findViewById(R.id.tvCheck); - btCheck.setText(DateHelper.formatHeaderDate(day)); - llButtonsHeader.addView(tvDay); - - day.add(GregorianCalendar.DAY_OF_MONTH, -1); - } - } - @Override public void onLoadFinished() { adapter.notifyDataSetChanged(); - updateEmptyMessage(); + helper.updateEmptyMessage(llEmpty); } @Override @@ -426,7 +209,7 @@ public boolean onOptionsItemSelected(MenuItem item) { EditHabitFragment frag = EditHabitFragment.createHabitFragment(); frag.setOnSavedListener(this); - frag.show(getFragmentManager(), "dialog"); + frag.show(getFragmentManager(), "editHabit"); return true; } @@ -485,8 +268,13 @@ private void selectItem(int position) if(actionMode == null) { - actionMode = getActivity().startActionMode(new ListHabitsActionBarCallback()); -// listView.setDragEnabled(false); + HabitSelectionCallback callback = new HabitSelectionCallback(activity, loader); + callback.setSelectedPositions(selectedPositions); + callback.setProgressBar(progressBar); + callback.setOnSavedListener(this); + callback.setListener(this); + + actionMode = getActivity().startActionMode(callback); } if(actionMode != null) actionMode.invalidate(); @@ -506,12 +294,6 @@ public void onSaved(Command command, Object savedObject) if(actionMode != null) actionMode.finish(); } - private void updateEmptyMessage() - { - if (loader.getLastLoadTimestamp() == null) llEmpty.setVisibility(View.GONE); - else llEmpty.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE); - } - @Override public boolean onLongClick(View v) { @@ -538,15 +320,14 @@ private void onCheckmarkLongClick(View v) private void toggleCheck(View v) { Long tag = (Long) v.getTag(R.string.habit_key); - Habit habit = loader.habits.get(tag); - - int offset = (Integer) v.getTag(R.string.offset_key); + Integer offset = (Integer) v.getTag(R.string.offset_key); long timestamp = DateHelper.getStartOfDay( DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay); - if (v.getTag(R.string.toggle_key).equals(2)) updateCheckmark(habit.color, (TextView) v, 0); - else updateCheckmark(habit.color, (TextView) v, 2); + Habit habit = loader.habits.get(tag); + if(habit == null) return; + helper.toggleCheckmarkView(v, habit); executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId()); } @@ -555,43 +336,6 @@ private void executeCommand(Command c, Long refreshKey) activity.executeCommand(c, refreshKey); } - private void hideHint() - { - llHint.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter() - { - @Override - public void onAnimationEnd(Animator animation) - { - llHint.setVisibility(View.GONE); - } - }); - } - - private void showNextHint() - { - Integer lastHintNumber = prefs.getInt("last_hint_number", -1); - Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1); - - if(DateHelper.getStartOfToday() > lastHintTimestamp) - showHint(lastHintNumber + 1); - } - - private void showHint(int hintNumber) - { - String[] hints = activity.getResources().getStringArray(R.array.hints); - if(hintNumber >= hints.length) return; - - prefs.edit().putInt("last_hint_number", hintNumber).apply(); - prefs.edit().putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply(); - - TextView tvContent = (TextView) llHint.findViewById(R.id.hintContent); - tvContent.setText(hints[hintNumber]); - - llHint.setAlpha(0.0f); - llHint.setVisibility(View.VISIBLE); - llHint.animate().alpha(1f).setDuration(500); - } - @Override public void drop(int from, int to) { @@ -614,226 +358,61 @@ public void onClick(View v) break; case R.id.llHint: - hideHint(); + hintManager.dismissHint(); break; } } - class ListHabitsAdapter extends BaseAdapter + public void onPostExecuteCommand(Long refreshKey) { - private LayoutInflater inflater; - private Typeface fontawesome; - - public ListHabitsAdapter(Context context) - { + if (refreshKey == null) loader.updateAllHabits(true); + else loader.updateHabit(refreshKey); + } - inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); - } + public void onActionModeDestroyed(ActionMode mode) + { + actionMode = null; + selectedPositions.clear(); + adapter.notifyDataSetChanged(); + listView.setDragEnabled(true); + } - @Override - public int getCount() - { - return loader.habits.size(); - } + public interface OnHabitClickListener + { + void onHabitClicked(Habit habit); + } - @Override - public Object getItem(int position) + private class HabitsDragSortController extends DragSortController + { + public HabitsDragSortController() { - return loader.habitsList.get(position); + super(ListHabitsFragment.this.listView); + setRemoveEnabled(false); } @Override - public long getItemId(int position) + public View onCreateFloatView(int position) { - return ((Habit) getItem(position)).getId(); + return adapter.getView(position, null, null); } @Override - public View getView(int position, View view, ViewGroup parent) - { - final Habit habit = loader.habitsList.get(position); - - if (view == null || - (Long) view.getTag(R.id.timestamp_key) != DateHelper.getStartOfToday()) - { - view = inflater.inflate(R.layout.list_habits_item, null); - ((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome); - - LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1); - view.findViewById(R.id.label).setLayoutParams(params); - - inflateCheckmarkButtons(view); - - view.setTag(R.id.timestamp_key, DateHelper.getStartOfToday()); - } - - TextView tvStar = ((TextView) view.findViewById(R.id.tvStar)); - TextView tvName = (TextView) view.findViewById(R.id.label); - LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); - LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons); - - llInner.setTag(R.string.habit_key, habit.getId()); - - updateNameAndIcon(habit, tvStar, tvName); - updateCheckmarkButtons(habit, llButtons); - - boolean selected = selectedPositions.contains(position); - if(selected) - llInner.setBackgroundResource(R.drawable.selected_box); - else - { - if (android.os.Build.VERSION.SDK_INT >= 21) - llInner.setBackgroundResource(R.drawable.ripple_white); - else - llInner.setBackgroundResource(R.drawable.card_background); - } - - return view; - } - - private void inflateCheckmarkButtons(View view) - { - for (int i = 0; i < buttonCount; i++) - { - View check = inflater.inflate(R.layout.list_habits_item_check, null); - TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); - btCheck.setTypeface(fontawesome); - btCheck.setOnLongClickListener(ListHabitsFragment.this); - btCheck.setOnClickListener(ListHabitsFragment.this); - ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); - } - } - } - - private void updateCheckmarkButtons(Habit habit, LinearLayout llButtons) - { - int activeColor = getActiveColor(habit); - int m = llButtons.getChildCount(); - Long habitId = habit.getId(); - - int isChecked[] = loader.checkmarks.get(habitId); - - for (int i = 0; i < m; i++) + public void onDestroyFloatView(View floatView) { - - TextView tvCheck = (TextView) llButtons.getChildAt(i); - tvCheck.setTag(R.string.habit_key, habitId); - tvCheck.setTag(R.string.offset_key, i); - if(isChecked.length > i) - updateCheckmark(activeColor, tvCheck, isChecked[i]); } } - private void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName) + private class HabitsDragListener implements DragSortListView.DragListener { - int activeColor = getActiveColor(habit); - - tvName.setText(habit.name); - tvName.setTextColor(activeColor); - - if (habit.isArchived()) + @Override + public void drag(int from, int to) { - tvStar.setText(getString(R.string.fa_archive)); - tvStar.setTextColor(activeColor); } - else - { - int score = loader.scores.get(habit.getId()); - if (score < Score.HALF_STAR_CUTOFF) - { - tvStar.setText(getString(R.string.fa_star_o)); - tvStar.setTextColor(INACTIVE_COLOR); - } - else if (score < Score.FULL_STAR_CUTOFF) - { - tvStar.setText(getString(R.string.fa_star_half_o)); - tvStar.setTextColor(INACTIVE_COLOR); - } - else - { - tvStar.setText(getString(R.string.fa_star)); - tvStar.setTextColor(activeColor); - } - } - } - - private int getActiveColor(Habit habit) - { - int activeColor = habit.color; - if(habit.isArchived()) activeColor = INACTIVE_COLOR; - - return activeColor; - } - - private void updateCheckmark(int activeColor, TextView tvCheck, int check) - { - switch (check) + @Override + public void startDrag(int position) { - case 2: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(activeColor); - tvCheck.setTag(R.string.toggle_key, 2); - break; - - case 1: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR); - tvCheck.setTag(R.string.toggle_key, 1); - break; - - case 0: - tvCheck.setText(R.string.fa_times); - tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR); - tvCheck.setTag(R.string.toggle_key, 0); - break; + selectItem(position); } } - - public void onPostExecuteCommand(Long refreshKey) - { - if (refreshKey == null) loader.updateAllHabits(true); - else loader.updateHabit(refreshKey); - } - - private void onExportHabitsClick(final LinkedList selectedHabits) - { - new AsyncTask() - { - String filename; - - @Override - protected void onPreExecute() - { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); - } - - @Override - protected void onPostExecute(Void aVoid) - { - if(filename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); - - startActivity(intent); - } - - progressBar.setVisibility(View.GONE); - } - - @Override - protected Void doInBackground(Void... params) - { - CSVExporter exporter = new CSVExporter(activity, selectedHabits); - filename = exporter.writeArchive(); - return null; - } - }.execute(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java index 66de86d12..fb5c04f60 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.fragments; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index 812f832ef..e1dde9c66 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.fragments; @@ -25,25 +28,34 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.TextView; import org.isoron.helpers.ColorHelper; import org.isoron.helpers.Command; import org.isoron.helpers.DialogHelper; +import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.ShowHabitActivity; +import org.isoron.uhabits.dialogs.HistoryEditorDialog; import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.views.HabitHistoryView; +import org.isoron.uhabits.views.HabitFrequencyView; import org.isoron.uhabits.views.HabitScoreView; import org.isoron.uhabits.views.HabitStreakView; import org.isoron.uhabits.views.RingView; -public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedListener +public class ShowHabitFragment extends Fragment + implements DialogHelper.OnSavedListener, HistoryEditorDialog.Listener { protected ShowHabitActivity activity; private Habit habit; + private HabitStreakView streakView; + private HabitScoreView scoreView; + private HabitHistoryView historyView; + private HabitFrequencyView punchcardView; @Override public void onStart() @@ -61,36 +73,75 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, habit.checkmarks.rebuild(); - if (android.os.Build.VERSION.SDK_INT >= 21) - { - int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f); - activity.getWindow().setStatusBarColor(darkerHabitColor); - } + Button btEditHistory = (Button) view.findViewById(R.id.btEditHistory); + streakView = (HabitStreakView) view.findViewById(R.id.streakView); + scoreView = (HabitScoreView) view.findViewById(R.id.scoreView); + historyView = (HabitHistoryView) view.findViewById(R.id.historyView); + punchcardView = (HabitFrequencyView) view.findViewById(R.id.punchcardView); - TextView tvHistory = (TextView) view.findViewById(R.id.tvHistory); - TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview); - TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength); - TextView tvStreaks = (TextView) view.findViewById(R.id.tvStreaks); - RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - HabitStreakView streakView = (HabitStreakView) view.findViewById(R.id.streakView); - HabitScoreView scoreView = (HabitScoreView) view.findViewById(R.id.scoreView); - HabitHistoryView historyView = (HabitHistoryView) view.findViewById(R.id.historyView); - - tvHistory.setTextColor(habit.color); - tvOverview.setTextColor(habit.color); - tvStrength.setTextColor(habit.color); - tvStreaks.setTextColor(habit.color); + updateHeaders(view); + updateScoreRing(view); - scoreRing.setColor(habit.color); - scoreRing.setPercentage((float) habit.scores.getNewestValue() / Score.MAX_SCORE); streakView.setHabit(habit); scoreView.setHabit(habit); historyView.setHabit(habit); + punchcardView.setHabit(habit); + + btEditHistory.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + HistoryEditorDialog frag = new HistoryEditorDialog(); + frag.setHabit(habit); + frag.setListener(ShowHabitFragment.this); + frag.show(getFragmentManager(), "historyEditor"); + } + }); + + if(savedInstanceState != null) + { + EditHabitFragment fragEdit = (EditHabitFragment) getFragmentManager() + .findFragmentByTag("editHabit"); + HistoryEditorDialog fragEditor = (HistoryEditorDialog) getFragmentManager() + .findFragmentByTag("historyEditor"); + + if(fragEdit != null) fragEdit.setOnSavedListener(this); + if(fragEditor != null) fragEditor.setListener(this); + } setHasOptionsMenu(true); return view; } + private void updateScoreRing(View view) + { + RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); + scoreRing.setColor(habit.color); + scoreRing.setPercentage((float) habit.scores.getNewestValue() / Score.MAX_SCORE); + } + + private void updateHeaders(View view) + { + if (android.os.Build.VERSION.SDK_INT >= 21) + { + int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f); + activity.getWindow().setStatusBarColor(darkerHabitColor); + } + + updateColor(view, R.id.tvHistory); + updateColor(view, R.id.tvOverview); + updateColor(view, R.id.tvStrength); + updateColor(view, R.id.tvStreaks); + updateColor(view, R.id.tvWeekdayFreq); + } + + private void updateColor(View view, int viewId) + { + TextView textView = (TextView) view.findViewById(viewId); + textView.setTextColor(habit.color); + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -106,7 +157,7 @@ public boolean onOptionsItemSelected(MenuItem item) { EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId()); frag.setOnSavedListener(this); - frag.show(getFragmentManager(), "dialog"); + frag.show(getFragmentManager(), "editHabit"); return true; } } @@ -125,4 +176,20 @@ public void onSaved(Command command, Object savedObject) ReminderHelper.createReminderAlarms(activity); activity.recreate(); } + + @Override + public void onHistoryEditorClosed() + { + refreshData(); + HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); + } + + public void refreshData() + { + streakView.refreshData(); + historyView.refreshData(); + scoreView.refreshData(); + punchcardView.refreshData(); + updateScoreRing(getView()); + } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java new file mode 100644 index 000000000..846283a63 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 Á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.helpers; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; +import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Score; + +import java.util.GregorianCalendar; + +public class ListHabitsHelper +{ + public static final int INACTIVE_COLOR = Color.rgb(200, 200, 200); + public static final int INACTIVE_CHECKMARK_COLOR = Color.rgb(230, 230, 230); + + private final Context context; + private final HabitListLoader loader; + private Typeface fontawesome; + + public ListHabitsHelper(Context context, HabitListLoader loader) + { + this.context = context; + this.loader = loader; + + fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + } + + public Typeface getFontawesome() + { + return fontawesome; + } + + public int getButtonCount() + { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + int width = (int) (dm.widthPixels / dm.density); + return Math.max(0, (int) ((width - 160) / 42.0)); + } + + public int getHabitNameWidth() + { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + int width = (int) (dm.widthPixels / dm.density); + return (int) ((width - 30 - getButtonCount() * 42) * dm.density); + } + + public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons) + { + int activeColor = getActiveColor(habit); + int m = llButtons.getChildCount(); + Long habitId = habit.getId(); + + int isChecked[] = loader.checkmarks.get(habitId); + + for (int i = 0; i < m; i++) + { + + TextView tvCheck = (TextView) llButtons.getChildAt(i); + tvCheck.setTag(R.string.habit_key, habitId); + tvCheck.setTag(R.string.offset_key, i); + if(isChecked.length > i) + updateCheckmark(activeColor, tvCheck, isChecked[i]); + } + } + + public int getActiveColor(Habit habit) + { + int activeColor = habit.color; + if(habit.isArchived()) activeColor = INACTIVE_COLOR; + + return activeColor; + } + + public void initializeLabelAndIcon(View itemView) + { + TextView tvStar = (TextView) itemView.findViewById(R.id.tvStar); + tvStar.setTypeface(getFontawesome()); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getHabitNameWidth(), + LinearLayout.LayoutParams.WRAP_CONTENT, 1); + itemView.findViewById(R.id.label).setLayoutParams(params); + } + + public void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName) + { + int activeColor = getActiveColor(habit); + + tvName.setText(habit.name); + tvName.setTextColor(activeColor); + + if (habit.isArchived()) + { + tvStar.setText(context.getString(R.string.fa_archive)); + tvStar.setTextColor(activeColor); + } + else + { + int score = loader.scores.get(habit.getId()); + + if (score < Score.HALF_STAR_CUTOFF) + { + tvStar.setText(context.getString(R.string.fa_star_o)); + tvStar.setTextColor(INACTIVE_COLOR); + } + else if (score < Score.FULL_STAR_CUTOFF) + { + tvStar.setText(context.getString(R.string.fa_star_half_o)); + tvStar.setTextColor(INACTIVE_COLOR); + } + else + { + tvStar.setText(context.getString(R.string.fa_star)); + tvStar.setTextColor(activeColor); + } + } + } + + public void updateCheckmark(int activeColor, TextView tvCheck, int check) + { + switch (check) + { + case 2: + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(activeColor); + tvCheck.setTag(R.string.toggle_key, 2); + break; + + case 1: + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR); + tvCheck.setTag(R.string.toggle_key, 1); + break; + + case 0: + tvCheck.setText(R.string.fa_times); + tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR); + tvCheck.setTag(R.string.toggle_key, 0); + break; + } + } + + public void updateHabitBackground(View view, boolean isSelected) + { + if (isSelected) + view.setBackgroundResource(R.drawable.selected_box); + else + { + if (android.os.Build.VERSION.SDK_INT >= 21) + view.setBackgroundResource(R.drawable.ripple_white); + else view.setBackgroundResource(R.drawable.card_background); + } + } + + public void inflateCheckmarkButtons(View view, View.OnLongClickListener onLongClickListener, + View.OnClickListener onClickListener, LayoutInflater inflater) + { + for (int i = 0; i < getButtonCount(); i++) + { + View check = inflater.inflate(R.layout.list_habits_item_check, null); + TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); + btCheck.setTypeface(fontawesome); + btCheck.setOnLongClickListener(onLongClickListener); + btCheck.setOnClickListener(onClickListener); + ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); + } + + view.setTag(R.id.timestamp_key, DateHelper.getStartOfToday()); + } + + public void updateHeader(ViewGroup header) + { + LayoutInflater inflater = LayoutInflater.from(context); + GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + header.removeAllViews(); + + for (int i = 0; i < getButtonCount(); i++) + { + View tvDay = inflater.inflate(R.layout.list_habits_header_check, null); + Button btCheck = (Button) tvDay.findViewById(R.id.tvCheck); + btCheck.setText(DateHelper.formatHeaderDate(day)); + header.addView(tvDay); + + day.add(GregorianCalendar.DAY_OF_MONTH, -1); + } + } + + public void updateEmptyMessage(View view) + { + if (loader.getLastLoadTimestamp() == null) view.setVisibility(View.GONE); + else view.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE); + } + + public void toggleCheckmarkView(View v, Habit habit) + { + if (v.getTag(R.string.toggle_key).equals(2)) + updateCheckmark(habit.color, (TextView) v, 0); + else + updateCheckmark(habit.color, (TextView) v, 2); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java index cbc1bd9c1..9a0e691d0 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.helpers; diff --git a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java index 4af223161..c1a2c9599 100644 --- a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2016 Á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.io; import android.content.Context; diff --git a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java index 204b5e243..8a0d93661 100644 --- a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java +++ b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java @@ -1,20 +1,20 @@ /* - * Copyright (C) 2016 Alinson Santos Xavier + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 . + * 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.loaders; diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index d62f2376f..e1007a26f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 61187ea30..04c8fe458 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index fc3163b21..02e3e67b6 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/Repetition.java b/app/src/main/java/org/isoron/uhabits/models/Repetition.java index 5ef848084..a2503706f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Repetition.java +++ b/app/src/main/java/org/isoron/uhabits/models/Repetition.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 6828101ab..f21038748 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -1,27 +1,39 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.From; import com.activeandroid.query.Select; import org.isoron.helpers.DateHelper; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; + public class RepetitionList { @@ -65,6 +77,8 @@ public Repetition getOldestNewerThan(long timestamp) public void toggle(long timestamp) { + timestamp = DateHelper.getStartOfDay(timestamp); + if (contains(timestamp)) { delete(timestamp); @@ -93,4 +107,53 @@ public boolean hasImplicitRepToday() int reps[] = habit.checkmarks.getValues(today - DateHelper.millisecondsInOneDay, today); return (reps[0] > 0); } + + public HashMap getWeekdayFrequency() + { + Repetition oldestRep = getOldest(); + if(oldestRep == null) return new HashMap<>(); + + String query = "select strftime('%Y', timestamp / 1000, 'unixepoch') as year," + + "strftime('%m', timestamp / 1000, 'unixepoch') as month," + + "strftime('%w', timestamp / 1000, 'unixepoch') as weekday, " + + "count(*) from repetitions " + + "where habit = ? " + + "group by year, month, weekday"; + + String[] params = { habit.getId().toString() }; + + SQLiteDatabase db = Cache.openDatabase(); + Cursor cursor = db.rawQuery(query, params); + + if(!cursor.moveToFirst()) return new HashMap<>(); + + HashMap map = new HashMap<>(); + GregorianCalendar date = DateHelper.getStartOfTodayCalendar(); + + do + { + int year = Integer.parseInt(cursor.getString(0)); + int month = Integer.parseInt(cursor.getString(1)); + int weekday = (Integer.parseInt(cursor.getString(2)) + 1) % 7; + int count = cursor.getInt(3); + + date.set(year, month - 1, 1); + long timestamp = date.getTimeInMillis(); + + Integer[] list = map.get(timestamp); + + if(list == null) + { + list = new Integer[7]; + Arrays.fill(list, 0); + map.put(timestamp, list); + } + + list[weekday] = count; + } + while (cursor.moveToNext()); + cursor.close(); + + return map; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index 44af97588..2c3cd1b9d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 8312a15e2..18b67758d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; @@ -123,6 +126,9 @@ public Integer getNewestValue() public int[] getAllValues(Long fromTimestamp, Long toTimestamp, Integer divisor) { + // Force rebuild of the score table + getNewestValue(); + Long offset = toTimestamp - (divisor - 1) * DateHelper.millisecondsInOneDay; String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " + diff --git a/app/src/main/java/org/isoron/uhabits/models/Streak.java b/app/src/main/java/org/isoron/uhabits/models/Streak.java index b5e8b5cac..35a20f444 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Streak.java +++ b/app/src/main/java/org/isoron/uhabits/models/Streak.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index d83a29d85..5f34e0634 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.models; diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java index 223217aad..d6b6fcd5c 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.views; diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java new file mode 100644 index 000000000..42c41b64b --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2016 Á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.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; + +import org.isoron.helpers.ColorHelper; +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.models.Habit; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; + +public class HabitFrequencyView extends ScrollableDataView +{ + + private Paint pGrid; + private float em; + private Habit habit; + private SimpleDateFormat dfMonth; + private SimpleDateFormat dfYear; + + private Paint pText, pGraph; + private RectF rect, prevRect; + private int baseSize; + private int paddingTop; + + private int columnWidth; + private int columnHeight; + private int nColumns; + + private int textColor; + private int dimmedTextColor; + private int[] colors; + private int primaryColor; + private boolean isBackgroundTransparent; + + private HashMap frequency; + private String wdays[]; + + public HabitFrequencyView(Context context, AttributeSet attrs) + { + super(context, attrs); + this.primaryColor = ColorHelper.palette[7]; + this.frequency = new HashMap<>(); + wdays = DateHelper.getShortDayNames(); + init(); + } + + public void setHabit(Habit habit) + { + this.habit = habit; + createColors(); + refreshData(); + postInvalidate(); + } + + private void init() + { + refreshData(); + createPaints(); + createColors(); + + dfMonth = new SimpleDateFormat("MMM", Locale.getDefault()); + dfYear = new SimpleDateFormat("yyyy", Locale.getDefault()); + + dfMonth.setTimeZone(TimeZone.getTimeZone("GMT")); + dfYear.setTimeZone(TimeZone.getTimeZone("GMT")); + + rect = new RectF(); + prevRect = new RectF(); + } + + private void createColors() + { + if(habit != null) + this.primaryColor = habit.color; + + if (isBackgroundTransparent) + { + primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f); + primaryColor = ColorHelper.setValue(primaryColor, 1.0f); + + textColor = Color.argb(192, 255, 255, 255); + dimmedTextColor = Color.argb(128, 255, 255, 255); + } + else + { + textColor = Color.argb(64, 0, 0, 0); + dimmedTextColor = Color.argb(16, 0, 0, 0); + } + + colors = new int[4]; + + colors[0] = Color.rgb(230, 230, 230); + colors[3] = primaryColor; + colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f); + colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); + } + + protected void createPaints() + { + pText = new Paint(); + pText.setAntiAlias(true); + + pGraph = new Paint(); + pGraph.setTextAlign(Paint.Align.CENTER); + pGraph.setAntiAlias(true); + + pGrid = new Paint(); + pGrid.setAntiAlias(true); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) + { + if(height < 9) height = 200; + + baseSize = height / 8; + setScrollerBucketSize(baseSize); + + columnWidth = baseSize; + columnHeight = 8 * baseSize; + nColumns = width / baseSize; + paddingTop = 0; + + pText.setTextSize(baseSize * 0.4f); + pGraph.setTextSize(baseSize * 0.4f); + pGraph.setStrokeWidth(baseSize * 0.1f); + pGrid.setStrokeWidth(baseSize * 0.05f); + em = pText.getFontSpacing(); + } + + public void refreshData() + { + if(isInEditMode()) + generateRandomData(); + else if(habit != null) + frequency = habit.repetitions.getWeekdayFrequency(); + + invalidate(); + } + + private void generateRandomData() + { + GregorianCalendar date = DateHelper.getStartOfTodayCalendar(); + date.set(Calendar.DAY_OF_MONTH, 1); + Random rand = new Random(); + frequency.clear(); + + for(int i = 0; i < 40; i++) + { + Integer values[] = new Integer[7]; + for(int j = 0; j < 7; j++) + values[j] = rand.nextInt(5); + + frequency.put(date.getTimeInMillis(), values); + date.add(Calendar.MONTH, -1); + } + } + + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + + rect.set(0, 0, nColumns * columnWidth, columnHeight); + rect.offset(0, paddingTop); + + drawGrid(canvas, rect); + + pText.setTextAlign(Paint.Align.CENTER); + pText.setColor(textColor); + pGraph.setColor(primaryColor); + prevRect.setEmpty(); + + GregorianCalendar currentDate = DateHelper.getStartOfTodayCalendar(); + + currentDate.set(Calendar.DAY_OF_MONTH, 1); + currentDate.add(Calendar.MONTH, -nColumns + 2 - getDataOffset()); + + for(int i = 0; i < nColumns - 1; i++) + { + rect.set(0, 0, columnWidth, columnHeight); + rect.offset(i * columnWidth, 0); + + drawColumn(canvas, rect, currentDate); + currentDate.add(Calendar.MONTH, 1); + } + } + + private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date) + { + Integer values[] = frequency.get(date.getTimeInMillis()); + float rowHeight = rect.height() / 8.0f; + prevRect.set(rect); + + for (int i = 0; i < 7; i++) + { + rect.set(0, 0, baseSize, baseSize); + rect.offset(prevRect.left, prevRect.top + columnWidth * i); + + if(values != null) + drawMarker(canvas, rect, values[i]); + + rect.offset(0, rowHeight); + } + + drawFooter(canvas, rect, date); + } + + private void drawFooter(Canvas canvas, RectF rect, GregorianCalendar date) + { + Date time = date.getTime(); + + canvas.drawText(dfMonth.format(time), rect.centerX(), rect.centerY() - 0.1f * em, pText); + + if(date.get(Calendar.MONTH) == 1) + canvas.drawText(dfYear.format(time), rect.centerX(), rect.centerY() + 0.9f * em, pText); + } + + private void drawMarker(Canvas canvas, RectF rect, Integer value) + { + float padding = rect.height() * 0.2f; + float radius = (rect.height() - 2 * padding) / 2.0f / 4.0f * Math.min(value, 4); + + pGraph.setColor(colors[Math.min(3, Math.max(0, value - 1))]); + canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph); + } + + private void drawGrid(Canvas canvas, RectF rGrid) + { + int nRows = 7; + float rowHeight = rGrid.height() / (nRows + 1); + + pText.setTextAlign(Paint.Align.LEFT); + pText.setColor(textColor); + pGrid.setColor(dimmedTextColor); + + for (int i = 0; i < nRows; i++) + { + canvas.drawText(wdays[i], rGrid.right - columnWidth, + rGrid.top + rowHeight / 2 + 0.25f * em, pText); + + pGrid.setStrokeWidth(1f); + canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid); + + rGrid.offset(0, rowHeight); + } + + canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid); + } + + public void setIsBackgroundTransparent(boolean isBackgroundTransparent) + { + this.isBackgroundTransparent = isBackgroundTransparent; + createColors(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index d99ba765b..67191b417 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.views; @@ -22,10 +25,13 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; +import android.os.AsyncTask; import android.util.AttributeSet; +import android.view.MotionEvent; import org.isoron.helpers.ColorHelper; import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import java.text.SimpleDateFormat; @@ -47,7 +53,6 @@ public class HabitHistoryView extends ScrollableDataView private int columnWidth; private int columnHeight; private int nColumns; - private int baseSize; private String wdays[]; private SimpleDateFormat dfMonth; @@ -62,11 +67,14 @@ public class HabitHistoryView extends ScrollableDataView private boolean isBackgroundTransparent; private int textColor; + private boolean isEditable; public HabitHistoryView(Context context, AttributeSet attrs) { super(context, attrs); this.primaryColor = ColorHelper.palette[7]; + this.checkmarks = new int[0]; + this.isEditable = false; init(); } @@ -74,12 +82,13 @@ public void setHabit(Habit habit) { this.habit = habit; createColors(); - fetchData(); + refreshData(); postInvalidate(); } private void init() { + refreshData(); createPaints(); createColors(); @@ -93,6 +102,7 @@ private void init() private void updateDate() { baseDate = new GregorianCalendar(); + baseDate.setTimeInMillis(DateHelper.getLocalTime()); baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7); nDays = (nColumns - 1) * 7; @@ -114,23 +124,42 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { if(height < 8) height = 200; - - baseSize = height / 8; + int baseSize = height / 8; setScrollerBucketSize(baseSize); - columnWidth = baseSize; - columnHeight = 8 * baseSize; - nColumns = width / baseSize; - squareSpacing = (int) Math.floor(baseSize / 15.0); - pSquareFg.setTextSize(baseSize * 0.5f); - pTextHeader.setTextSize(baseSize * 0.5f); + int maxTextSize = getResources().getDimensionPixelSize(R.dimen.history_max_font_size); + float textSize = Math.min(baseSize * 0.5f, maxTextSize); + + pSquareFg.setTextSize(textSize); + pTextHeader.setTextSize(textSize); squareTextOffset = pSquareFg.getFontSpacing() * 0.4f; headerTextOffset = pTextHeader.getFontSpacing() * 0.3f; + int rightLabelWidth = getWeekdayLabelWidth(); + int horizontalPadding = getPaddingRight() + getPaddingLeft(); + + columnWidth = baseSize; + columnHeight = 8 * baseSize; + nColumns = (width - rightLabelWidth - horizontalPadding) / baseSize + 1; + updateDate(); } + private int getWeekdayLabelWidth() + { + int width = 0; + Rect bounds = new Rect(); + + for(String w : wdays) + { + pSquareFg.getTextBounds(w, 0, w.length(), bounds); + width = Math.max(width, bounds.right); + } + + return width; + } + private void createColors() { if(habit != null) @@ -176,22 +205,18 @@ protected void createPaints() pSquareFg.setTextAlign(Align.CENTER); } - protected void fetchData() + public void refreshData() { if(isInEditMode()) generateRandomData(); else { - if(habit == null) - { - checkmarks = new int[0]; - return; - } - + if(habit == null) return; checkmarks = habit.checkmarks.getAllValues(); } updateDate(); + invalidate(); } private void generateRandomData() @@ -223,6 +248,7 @@ protected void onDraw(Canvas canvas) super.onDraw(canvas); baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing); + baseLocation.offset(getPaddingLeft(), getPaddingTop()); previousMonth = ""; previousYear = ""; @@ -326,4 +352,59 @@ public void setIsBackgroundTransparent(boolean isBackgroundTransparent) this.isBackgroundTransparent = isBackgroundTransparent; createColors(); } + + @Override + public boolean onSingleTapUp(MotionEvent e) + { + if(!isEditable) return false; + + int pointerId = e.getPointerId(0); + float x = e.getX(pointerId); + float y = e.getY(pointerId); + + final Long timestamp = positionToTimestamp(x, y); + if(timestamp == null) return false; + + new AsyncTask() + { + @Override + protected Void doInBackground(Void... params) + { + habit.repetitions.toggle(timestamp); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) + { + refreshData(); + invalidate(); + } + }.execute(); + + return true; + } + + private Long positionToTimestamp(float x, float y) + { + int col = (int) (x / columnWidth); + int row = (int) (y / columnWidth); + + if(row == 0) return null; + if(col == nColumns - 1) return null; + + int offset = col * 7 + (row - 1); + Calendar date = (Calendar) baseDate.clone(); + date.add(Calendar.DAY_OF_YEAR, offset); + + if(DateHelper.getStartOfDay(date.getTimeInMillis()) > DateHelper.getStartOfToday()) + return null; + + return date.getTimeInMillis(); + } + + public void setIsEditable(boolean isEditable) + { + this.isEditable = isEditable; + } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 68855e50a..02f596e0b 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.views; @@ -68,6 +71,7 @@ public HabitScoreView(Context context, AttributeSet attrs) { super(context, attrs); this.primaryColor = ColorHelper.palette[7]; + this.scores = new int[0]; init(); } @@ -75,12 +79,13 @@ public void setHabit(Habit habit) { this.habit = habit; createColors(); - fetchData(); + refreshData(); postInvalidate(); } private void init() { + refreshData(); createPaints(); createColors(); @@ -159,21 +164,17 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) em = pText.getFontSpacing(); } - protected void fetchData() + public void refreshData() { if(isInEditMode()) generateRandomData(); else { - if (habit == null) - { - scores = new int[0]; - return; - } - + if (habit == null) return; scores = habit.scores.getAllValues(BUCKET_SIZE * DateHelper.millisecondsInOneDay); } + invalidate(); } private void generateRandomData() diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index 5872d96c9..1fc61cc0c 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.views; @@ -62,6 +65,7 @@ public HabitStreakView(Context context, AttributeSet attrs) { super(context, attrs); this.primaryColor = ColorHelper.palette[7]; + startTimes = endTimes = lengths = new long[0]; init(); } @@ -70,12 +74,13 @@ public void setHabit(Habit habit) this.habit = habit; createColors(); - fetchData(); + refreshData(); postInvalidate(); } private void init() { + refreshData(); createPaints(); createColors(); @@ -154,17 +159,13 @@ protected void createPaints() pBar.setAntiAlias(true); } - protected void fetchData() + public void refreshData() { if(isInEditMode()) generateRandomData(); else { - if(habit == null) - { - startTimes = endTimes = lengths = new long[0]; - return; - } + if(habit == null) return; List streaks = habit.streaks.getAll(); int size = streaks.size(); @@ -184,6 +185,8 @@ protected void fetchData() maxStreakLength = Math.max(maxStreakLength, s.length); } } + + invalidate(); } private void generateRandomData() diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index e897fb5fb..80c04c095 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.views; diff --git a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java b/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java index 7360456da..8ad2be7c5 100644 --- a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java +++ b/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java @@ -1,20 +1,20 @@ /* - * Copyright (C) 2016 Alinson Santos Xavier + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 . + * 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.views; @@ -58,8 +58,6 @@ private void init(Context context) scrollAnimator.addUpdateListener(this); } - protected abstract void fetchData(); - @Override public boolean onTouchEvent(MotionEvent event) { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index d890ea1b3..6f95d5713 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index 2710435f9..849fdf52e 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java new file mode 100644 index 000000000..8ea2248df --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Á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.widgets; + +import android.app.PendingIntent; +import android.content.Context; +import android.view.View; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitFrequencyView; + +public class FrequencyWidgetProvider extends BaseWidgetProvider +{ + @Override + protected View buildCustomView(Context context, Habit habit) + { + HabitFrequencyView view = new HabitFrequencyView(context, null); + view.setIsBackgroundTransparent(true); + view.setHabit(habit); + return view; + } + + @Override + protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) + { + return null; + } + + @Override + protected int getDefaultHeight() + { + return 200; + } + + @Override + protected int getDefaultWidth() + { + return 200; + } + + @Override + protected int getLayoutId() + { + return R.layout.widget_graph; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java index f44127cac..d09881acf 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index 7f6b50aea..2fd440910 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index 3cede1dee..5eb9c1097 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index c125654d2..40916a8fc 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -1,17 +1,20 @@ -/* Copyright (C) 2016 Alinson Santos Xavier +/* + * Copyright (C) 2016 Álinson Santos Xavier * - * This program 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. + * This file is part of Loop Habit Tracker. * - * This program 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 + * 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 . + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . */ package org.isoron.uhabits.widgets; diff --git a/app/src/main/res/drawable-v21/habits_item_check_normal.xml b/app/src/main/res/drawable-v21/habits_item_check_normal.xml index db520e5c8..48bf29073 100644 --- a/app/src/main/res/drawable-v21/habits_item_check_normal.xml +++ b/app/src/main/res/drawable-v21/habits_item_check_normal.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable-v21/habits_item_check_pressed.xml b/app/src/main/res/drawable-v21/habits_item_check_pressed.xml index f912c3dfc..372605fdd 100644 --- a/app/src/main/res/drawable-v21/habits_item_check_pressed.xml +++ b/app/src/main/res/drawable-v21/habits_item_check_pressed.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable-v21/ripple_transparent.xml b/app/src/main/res/drawable-v21/ripple_transparent.xml index 856240bf1..77c8ff31b 100644 --- a/app/src/main/res/drawable-v21/ripple_transparent.xml +++ b/app/src/main/res/drawable-v21/ripple_transparent.xml @@ -1,3 +1,22 @@ + + diff --git a/app/src/main/res/drawable-v21/ripple_white.xml b/app/src/main/res/drawable-v21/ripple_white.xml index cdf2f4fa4..5721b50cb 100644 --- a/app/src/main/res/drawable-v21/ripple_white.xml +++ b/app/src/main/res/drawable-v21/ripple_white.xml @@ -1,3 +1,22 @@ + + diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_background.xml index 0aa19dcb2..60d7d0fa4 100644 --- a/app/src/main/res/drawable/card_background.xml +++ b/app/src/main/res/drawable/card_background.xml @@ -1,4 +1,23 @@ + + + + diff --git a/app/src/main/res/drawable/habits_item_check_pressed.xml b/app/src/main/res/drawable/habits_item_check_pressed.xml index 887279103..1760e40d2 100644 --- a/app/src/main/res/drawable/habits_item_check_pressed.xml +++ b/app/src/main/res/drawable/habits_item_check_pressed.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable/habits_list_header_background.xml b/app/src/main/res/drawable/habits_list_header_background.xml index a935ad4de..83385eaa5 100644 --- a/app/src/main/res/drawable/habits_list_header_background.xml +++ b/app/src/main/res/drawable/habits_list_header_background.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable/selected_box.xml b/app/src/main/res/drawable/selected_box.xml index f311ac13d..754676620 100644 --- a/app/src/main/res/drawable/selected_box.xml +++ b/app/src/main/res/drawable/selected_box.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable/widget_background.xml b/app/src/main/res/drawable/widget_background.xml index 1c66dd34c..2591a3c4f 100644 --- a/app/src/main/res/drawable/widget_background.xml +++ b/app/src/main/res/drawable/widget_background.xml @@ -1,4 +1,23 @@ + + diff --git a/app/src/main/res/drawable/widget_preview_frequency.png b/app/src/main/res/drawable/widget_preview_frequency.png new file mode 100644 index 000000000..a3fc86505 Binary files /dev/null and b/app/src/main/res/drawable/widget_preview_frequency.png differ diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml new file mode 100644 index 000000000..c183cb477 --- /dev/null +++ b/app/src/main/res/layout/about.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_habit.xml index 17881a36a..2dbbeab8d 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_habit.xml @@ -1,3 +1,22 @@ + + + ~ + ~ 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 . + --> + + + + +