Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53c9dca3e2 | |||
| 8b37daa9fb | |||
| f9c2e83c79 | |||
| 71bdc70c1a | |||
| 5e4a40579a | |||
| d326be1224 | |||
| fb8a09c95c | |||
| 1635b9905d | |||
| 2d88fc0b20 | |||
| 1d74359c06 | |||
| f75f77cec7 | |||
| 8b10138cd6 | |||
| 2b40633110 | |||
| 1102d05a61 | |||
| 51e8c2f111 | |||
| 547e4e5f63 | |||
| 84d5c2aac6 | |||
| 2b3b423fa3 | |||
| 3b28c37c5e | |||
| e749e787ad | |||
| 9c5d582f24 | |||
| 75a4edb61c | |||
| 34c0758308 | |||
| 85963ae061 | |||
| e3390d5397 | |||
| 59a2f31a73 | |||
| c01f8450d3 | |||
| cea5241135 | |||
| 7784fc5c75 | |||
| 6dd017f33e | |||
| c8cd9f85f6 | |||
| d038bdb741 | |||
| f55e8d2c85 | |||
| f8dc1d9eae | |||
| 85393b0d40 | |||
| 75599ad20c | |||
| c6b948cbf5 | |||
| 4d42133a4b | |||
| 5b151805ff | |||
| aa86826bdb | |||
| 821373a340 | |||
| 8f37e293b1 | |||
| 0fb8ed0b53 | |||
| 0c696b2eb2 | |||
| cd127bc3f8 | |||
| 2cfc809490 | |||
| ba31dee16a | |||
| 146c743fb8 | |||
| 0c00e9ec2d | |||
| 49af55a2de | |||
| 0114b48197 | |||
| 09f615a5e6 | |||
| 9014acc548 | |||
| 4fb386be86 | |||
| ea8606be97 | |||
| e0527dc8ff | |||
| aaf2789a21 | |||
| f8dc64cc6b | |||
| ced5b751be | |||
| 8a60dda74e | |||
| c8c4df6ef7 | |||
| 0c0ac9dee5 | |||
| fdf6c91929 | |||
| 08d6e39a17 | |||
| b9bc7bd1b5 | |||
| 7b73238448 | |||
| 83ccfb82ac | |||
| 199d35d9c7 | |||
| 382a2fe600 | |||
| e02f9c1d60 | |||
| 5e7636d7ff | |||
| 616322cd35 | |||
| 299c6a0c1d | |||
| b4911b6cb4 | |||
| f41f877107 | |||
| 58aa7f6687 | |||
| d196e01da0 | |||
| 1fbd12a947 | |||
| 7493291ade | |||
| 2a750704d9 | |||
| eb79113e4c | |||
| cb2f3823cd | |||
| 39e29dabb8 | |||
| 51d1b93d03 | |||
| 8acbc63914 | |||
| ac8e78ff24 | |||
| 162ded66d8 | |||
| 5428209543 | |||
| 141fd30d70 | |||
| 48d446a243 | |||
| ae7869d3a2 | |||
| b8cacaffa9 | |||
| 4def8f0409 | |||
| f0d12e9925 | |||
| a2331260e4 | |||
| c1a846d42b | |||
| 031d684b3e | |||
| 3a770e71e3 | |||
| b29dd8ea79 | |||
| 7f1553a4a1 | |||
| d748f5d6de | |||
| 7234e072e6 | |||
| 7f71f46367 | |||
| c1dae021bf | |||
| 88455acc76 | |||
| 6a1cb09ca2 | |||
| 33d7ab52ca | |||
| d2682358c2 | |||
| f511ca2028 | |||
| d5774e8511 | |||
| b6e7e72f5a | |||
| 27220c9ab2 | |||
| f1424e5820 | |||
| a18e0fbda0 | |||
| 4c3a72df81 | |||
| e9ce50f686 | |||
| acb26964f3 | |||
| 917d1218ae | |||
| f5ccd7d8c3 | |||
| 456a9e49a9 | |||
| 6b05004647 | |||
| f9a9339042 | |||
| 5e21d877c5 | |||
| af0ef90e4d | |||
| 02b3ea58cf | |||
| 0dcedb3c59 | |||
| 637d75fd2e |
34
CHANGELOG.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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
|
||||
* Widgets (checkmark, history, score and streaks)
|
||||
* More natural scrolling on data views (fling)
|
||||
* Minor UI improvements on pre-Lollipop devices
|
||||
* Fix crash on Samsung Galaxy TabS 8.4
|
||||
* Other minor bug fixes
|
||||
|
||||
### 1.1.1 (February 24, 2016)
|
||||
|
||||
* Show reminder only on chosen days of the week
|
||||
* Rearrange habits by long-pressing then dragging
|
||||
* Select and modify multiple habits simultaneously
|
||||
* 12/24 hour format according to phone preferences
|
||||
* Permanently delete habits
|
||||
* Usage hints during startup
|
||||
* Translation to Brazilian Portuguese and Chinese
|
||||
* Other minor fixes
|
||||
|
||||
### 1.0.0 (February 19, 2016)
|
||||
|
||||
* Initial release
|
||||
91
NOTICE.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Copyright Notices
|
||||
|
||||
### ActiveAndroid
|
||||
|
||||
<https://github.com/pardom/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
|
||||
|
||||
<https://source.android.com/>
|
||||
|
||||
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
|
||||
|
||||
<http://fontawesome.io>
|
||||
|
||||
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
|
||||
|
||||
<https://github.com/bauerca/drag-sort-listview>
|
||||
|
||||
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
|
||||
|
||||
<https://github.com/google/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).
|
||||
|
||||
92
README.md
@@ -1,16 +1,43 @@
|
||||
# Loop Habit Tracker
|
||||
|
||||
Loop is a simple Android app that helps you create and maintain good habits. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source, with no intrusive permissions. Join the open beta at [Google Play Store](https://play.google.com/apps/testing/org.isoron.uhabits).
|
||||
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.
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
|
||||
## Features
|
||||
|
||||
* Simple and beautiful interface, following the Material Design guidelines.
|
||||
* Advanced algorithms 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, showing how did you habits improve over time. Scroll back to see the complete history of your habit.
|
||||
* Support for both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day.
|
||||
* Habit reminders at a chosen hour of the day.
|
||||
* Support for Android Wear. Reminders can be checked or dismissed from the 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.
|
||||
* **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.
|
||||
|
||||
* **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.
|
||||
|
||||
* **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.
|
||||
|
||||
* **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
|
||||
|
||||
@@ -18,12 +45,61 @@ Loop is a simple Android app that helps you create and maintain good habits. Det
|
||||
[![Edit habit][screen2th]][screen2]
|
||||
[![Habit strength][screen3th]][screen3]
|
||||
[![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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
[screen1]: screenshots/original/uhabits1.png
|
||||
[screen2]: screenshots/original/uhabits2.png
|
||||
[screen3]: screenshots/original/uhabits3.png
|
||||
[screen4]: screenshots/original/uhabits4.png
|
||||
[screen5]: screenshots/original/uhabits5.png
|
||||
[screen1th]: screenshots/thumbs/uhabits1.png
|
||||
[screen2th]: screenshots/thumbs/uhabits2.png
|
||||
[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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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<Habit> withName(final String name)
|
||||
{
|
||||
return new TypeSafeMatcher<Habit>()
|
||||
{
|
||||
@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<View> containsHabit(final Matcher<Habit> matcher)
|
||||
{
|
||||
return new TypeSafeMatcher<View>()
|
||||
{
|
||||
@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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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<View> 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<View> 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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<String> 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<String> names)
|
||||
{
|
||||
for(String name : names)
|
||||
onView(withId(R.id.listView))
|
||||
.check(matches(not(containsHabit(withName(name)))));
|
||||
}
|
||||
|
||||
public static void assertHabitExists(String name)
|
||||
{
|
||||
List<String> names = new LinkedList<>();
|
||||
names.add(name);
|
||||
assertHabitsExist(names);
|
||||
}
|
||||
|
||||
public static void assertHabitsExist(List<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
app/src/androidTest/java/org/isoron/uhabits/MainTest.java
Normal file
@@ -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<MainActivity> 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<String> 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="8"
|
||||
android:versionName="1.1.1">
|
||||
android:versionCode="10"
|
||||
android:versionName="1.3.0">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.VIBRATE"/>
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18"/>
|
||||
|
||||
<application
|
||||
android:name="com.activeandroid.app.Application"
|
||||
android:allowBackup="true"
|
||||
android:backupAgent=".HabitsBackupAgent"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/main_activity_title"
|
||||
android:theme="@style/AppBaseTheme"
|
||||
android:backupAgent=".HabitsBackupAgent">
|
||||
android:theme="@style/AppBaseTheme">
|
||||
|
||||
<meta-data
|
||||
android:name="AA_DB_NAME"
|
||||
android:value="uhabits.db"/>
|
||||
|
||||
<meta-data
|
||||
android:name="AA_DB_VERSION"
|
||||
android:value="11"/>
|
||||
|
||||
android:value="12"/>
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" />
|
||||
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/main_activity_title">
|
||||
<intent-filter
|
||||
android:label="@string/app_name">
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".ReminderAlarmReceiver" />
|
||||
|
||||
<activity
|
||||
android:name=".ShowHabitActivity"
|
||||
android:label="@string/title_activity_show_habit"
|
||||
@@ -58,9 +80,87 @@
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".IntroActivity"
|
||||
<activity
|
||||
android:name=".IntroActivity"
|
||||
android:label=""
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
|
||||
|
||||
<activity
|
||||
android:name=".widgets.HabitPickerDialog"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/about"
|
||||
android:parentActivityName=".MainActivity">
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.CheckmarkWidgetProvider"
|
||||
android:label="@string/checkmark">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_checkmark_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.HistoryWidgetProvider"
|
||||
android:label="@string/history">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_history_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.ScoreWidgetProvider"
|
||||
android:label="@string/habit_strength">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_score_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.StreakWidgetProvider"
|
||||
android:label="@string/streaks">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_streak_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.FrequencyWidgetProvider"
|
||||
android:label="@string/frequency">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_frequency_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".HabitBroadcastReceiver"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
3
app/src/main/assets/migrations/12.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
delete from Score;
|
||||
delete from Streak;
|
||||
delete from Checkmarks;
|
||||
BIN
app/src/main/ic_small_widget_preview-web.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
@@ -23,6 +23,7 @@ import java.util.Locale;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActionBar.LayoutParams;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
@@ -132,6 +133,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
// Empty constructor required for dialog fragment.
|
||||
}
|
||||
|
||||
@SuppressLint("Java")
|
||||
public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
|
||||
int hourOfDay, int minute, boolean is24HourMode) {
|
||||
// Empty constructor required for dialog fragment.
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
@@ -57,4 +60,35 @@ public class ColorHelper
|
||||
|
||||
return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL;
|
||||
}
|
||||
|
||||
public static int setHue(int color, float newHue)
|
||||
{
|
||||
return setHSVParameter(color, newHue, 0);
|
||||
}
|
||||
|
||||
public static int setSaturation(int color, float newSaturation)
|
||||
{
|
||||
return setHSVParameter(color, newSaturation, 1);
|
||||
}
|
||||
|
||||
public static int setValue(int color, float newValue)
|
||||
{
|
||||
return setHSVParameter(color, newValue, 2);
|
||||
}
|
||||
|
||||
public static int setMinValue(int color, float newValue)
|
||||
{
|
||||
float hsv[] = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[2] = Math.max(hsv[2], newValue);
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
private static int setHSVParameter(int color, float newValue, int index)
|
||||
{
|
||||
float hsv[] = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[index] = newValue;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
|
||||
public abstract class DialogHelper
|
||||
{
|
||||
|
||||
public static final String ISORON_NAMESPACE = "http://isoron.org/android";
|
||||
private static Typeface fontawesome;
|
||||
|
||||
public interface OnSavedListener
|
||||
@@ -60,9 +64,32 @@ public abstract class DialogHelper
|
||||
prefs.edit().putInt("launch_count", count + 1).apply();
|
||||
}
|
||||
|
||||
public static void updateLastAppVersion(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply();
|
||||
}
|
||||
|
||||
public static int getLaunchCount(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getInt("launch_count", 0);
|
||||
}
|
||||
|
||||
public static String getAttribute(Context context, AttributeSet attrs, String name)
|
||||
{
|
||||
int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0);
|
||||
|
||||
if(resId != 0)
|
||||
return context.getResources().getString(resId);
|
||||
else
|
||||
return attrs.getAttributeValue(ISORON_NAMESPACE, name);
|
||||
}
|
||||
|
||||
public static float dpToPixels(Context context, float dp)
|
||||
{
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
96
app/src/main/java/org/isoron/uhabits/AboutActivity.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
@@ -31,6 +34,7 @@ import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
@@ -38,12 +42,11 @@ import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
|
||||
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
|
||||
public static final String ACTION_REMIND = "org.isoron.uhabits.ACTION_REMIND";
|
||||
public static final String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER";
|
||||
public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
|
||||
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
|
||||
|
||||
@Override
|
||||
@@ -51,7 +54,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
{
|
||||
switch (intent.getAction())
|
||||
{
|
||||
case ACTION_REMIND:
|
||||
case ACTION_SHOW_REMINDER:
|
||||
createNotification(context, intent);
|
||||
createReminderAlarms(context);
|
||||
break;
|
||||
@@ -97,15 +100,23 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
private void checkHabit(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
Long timestamp = DateHelper.getStartOfToday();
|
||||
String paramTimestamp = data.getQueryParameter("timestamp");
|
||||
|
||||
if(paramTimestamp != null) timestamp = Long.parseLong(paramTimestamp);
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
|
||||
Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
habit.toggleRepetition(timestamp);
|
||||
habit.repetitions.toggle(timestamp);
|
||||
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);
|
||||
|
||||
MainActivity.updateWidgets(context);
|
||||
}
|
||||
|
||||
private void dismissAllHabits()
|
||||
@@ -131,8 +142,10 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
|
||||
|
||||
if (habit.hasImplicitRepToday()) return;
|
||||
if (habit.repetitions.hasImplicitRepToday()) return;
|
||||
|
||||
habit.highlight = 1;
|
||||
habit.save();
|
||||
@@ -147,24 +160,12 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
PendingIntent contentPendingIntent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
|
||||
Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class);
|
||||
deleteIntent.setAction(ACTION_DISMISS);
|
||||
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
||||
|
||||
Intent checkIntent = new Intent(context, ReminderAlarmReceiver.class);
|
||||
checkIntent.setData(data);
|
||||
checkIntent.setAction(ACTION_CHECK);
|
||||
PendingIntent checkIntentPending = PendingIntent.getBroadcast(context, 0, checkIntent, 0);
|
||||
|
||||
Intent snoozeIntent = new Intent(context, ReminderAlarmReceiver.class);
|
||||
snoozeIntent.setData(data);
|
||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||
PendingIntent snoozeIntentPending = PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
||||
PendingIntent dismissPendingIntent = buildDismissIntent(context);
|
||||
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
|
||||
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
|
||||
|
||||
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
|
||||
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender =
|
||||
new NotificationCompat.WearableExtender().setBackground(
|
||||
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
|
||||
@@ -174,7 +175,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
.setContentTitle(habit.name)
|
||||
.setContentText(habit.description)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.setDeleteIntent(deletePendingIntent)
|
||||
.setDeleteIntent(dismissPendingIntent)
|
||||
.addAction(R.drawable.ic_action_check,
|
||||
context.getString(R.string.check), checkIntentPending)
|
||||
.addAction(R.drawable.ic_action_snooze,
|
||||
@@ -194,6 +195,32 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
|
||||
notificationManager.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
snoozeIntent.setData(data);
|
||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
||||
}
|
||||
|
||||
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
checkIntent.setData(data);
|
||||
checkIntent.setAction(ACTION_CHECK);
|
||||
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
||||
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildDismissIntent(Context context)
|
||||
{
|
||||
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
deleteIntent.setAction(ACTION_DISMISS);
|
||||
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
||||
}
|
||||
|
||||
private boolean checkWeekday(Intent intent, Habit habit)
|
||||
{
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
@@ -30,12 +40,21 @@ import org.isoron.helpers.ReplayableActivity;
|
||||
import org.isoron.uhabits.fragments.ListHabitsFragment;
|
||||
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;
|
||||
|
||||
public class MainActivity extends ReplayableActivity
|
||||
implements ListHabitsFragment.OnHabitClickListener
|
||||
{
|
||||
private ListHabitsFragment listHabitsFragment;
|
||||
SharedPreferences prefs;
|
||||
private SharedPreferences prefs;
|
||||
private BroadcastReceiver receiver;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
@@ -47,15 +66,30 @@ public class MainActivity extends ReplayableActivity
|
||||
listHabitsFragment =
|
||||
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
|
||||
|
||||
receiver = new Receiver();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
|
||||
|
||||
onStartup();
|
||||
}
|
||||
|
||||
private void onStartup()
|
||||
{
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
ReminderHelper.createReminderAlarms(MainActivity.this);
|
||||
DialogHelper.incrementLaunchCount(this);
|
||||
DialogHelper.updateLastAppVersion(this);
|
||||
showTutorial();
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
ReminderHelper.createReminderAlarms(MainActivity.this);
|
||||
updateWidgets(MainActivity.this);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void showTutorial()
|
||||
@@ -87,9 +121,18 @@ public class MainActivity extends ReplayableActivity
|
||||
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);
|
||||
@@ -108,5 +151,50 @@ public class MainActivity extends ReplayableActivity
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
listHabitsFragment.onPostExecuteCommand(refreshKey);
|
||||
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
updateWidgets(MainActivity.this);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context)
|
||||
{
|
||||
updateWidgets(context, CheckmarkWidgetProvider.class);
|
||||
updateWidgets(context, HistoryWidgetProvider.class);
|
||||
updateWidgets(context, ScoreWidgetProvider.class);
|
||||
updateWidgets(context, StreakWidgetProvider.class);
|
||||
updateWidgets(context, FrequencyWidgetProvider.class);
|
||||
}
|
||||
|
||||
private static void updateWidgets(Context context, Class providerClass)
|
||||
{
|
||||
ComponentName provider = new ComponentName(context, providerClass);
|
||||
Intent intent = new Intent(context, providerClass);
|
||||
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
|
||||
int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
localBroadcastManager.unregisterReceiver(receiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
class Receiver extends BroadcastReceiver
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
listHabitsFragment.onPostExecuteCommand(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
@@ -1,36 +1,47 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
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 @@ public class ShowHabitActivity extends ReplayableActivity
|
||||
|
||||
Uri data = getIntent().getData();
|
||||
habit = Habit.get(ContentUris.parseId(data));
|
||||
getActionBar().setTitle(habit.name);
|
||||
ActionBar actionBar = getActionBar();
|
||||
|
||||
if(actionBar != null)
|
||||
{
|
||||
actionBar.setTitle(habit.name);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color));
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(habit.color));
|
||||
}
|
||||
|
||||
setContentView(R.layout.show_habit_activity);
|
||||
|
||||
fragment = (ShowHabitFragment) getFragmentManager().findFragmentById(R.id.fragment2);
|
||||
|
||||
receiver = new Receiver();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(receiver,
|
||||
new IntentFilter(MainActivity.ACTION_REFRESH));
|
||||
}
|
||||
|
||||
class Receiver extends BroadcastReceiver
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
fragment.refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
protected void onDestroy()
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.show_habit_activity_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_settings:
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
localBroadcastManager.unregisterReceiver(receiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
@@ -23,7 +26,6 @@ import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChangeHabitColorCommand extends Command
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class CreateHabitCommand extends Command
|
||||
{
|
||||
private Habit model;
|
||||
private Long savedId;
|
||||
|
||||
public CreateHabitCommand(Habit model)
|
||||
{
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit savedHabit = new Habit(model);
|
||||
if (savedId == null)
|
||||
{
|
||||
savedHabit.save();
|
||||
savedId = savedHabit.getId();
|
||||
}
|
||||
else
|
||||
{
|
||||
savedHabit.save(savedId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
Habit.get(savedId).delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
@@ -36,12 +39,14 @@ public class DeleteHabitsCommand extends Command
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.cascadeDelete();
|
||||
|
||||
Habit.rebuildOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class EditHabitCommand extends Command
|
||||
{
|
||||
private Habit original;
|
||||
private Habit modified;
|
||||
private long savedId;
|
||||
private boolean hasIntervalChanged;
|
||||
|
||||
public EditHabitCommand(Habit original, Habit modified)
|
||||
{
|
||||
this.savedId = original.getId();
|
||||
this.modified = new Habit(modified);
|
||||
this.original = new Habit(original);
|
||||
|
||||
hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) ||
|
||||
!this.original.freqNum.equals(this.modified.freqNum));
|
||||
}
|
||||
|
||||
public void execute()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(modified);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.checkmarks.deleteNewerThan(0);
|
||||
habit.streaks.deleteNewerThan(0);
|
||||
habit.scores.deleteNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void undo()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(original);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.checkmarks.deleteNewerThan(0);
|
||||
habit.streaks.deleteNewerThan(0);
|
||||
habit.scores.deleteNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed_back;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
private Long offset;
|
||||
private Habit habit;
|
||||
|
||||
public ToggleRepetitionCommand(Habit habit, long offset)
|
||||
{
|
||||
this.offset = offset;
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
habit.repetitions.toggle(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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<Integer> 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<Integer> 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<Habit> 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<Habit> selectedHabits)
|
||||
{
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
@@ -40,6 +43,8 @@ import org.isoron.helpers.Command;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.helpers.DialogHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
@@ -55,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();
|
||||
@@ -71,7 +83,7 @@ public class EditHabitFragment extends DialogFragment
|
||||
return frag;
|
||||
}
|
||||
|
||||
static EditHabitFragment createHabitFragment()
|
||||
public static EditHabitFragment createHabitFragment()
|
||||
{
|
||||
EditHabitFragment frag = new EditHabitFragment();
|
||||
Bundle args = new Bundle();
|
||||
@@ -94,13 +106,13 @@ public class EditHabitFragment extends DialogFragment
|
||||
|
||||
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());
|
||||
|
||||
@@ -132,14 +144,27 @@ public class EditHabitFragment extends DialogFragment
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -223,8 +248,6 @@ public class EditHabitFragment extends DialogFragment
|
||||
|
||||
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());
|
||||
@@ -237,15 +260,18 @@ public class EditHabitFragment extends DialogFragment
|
||||
editor.putInt("pref_default_habit_freq_den", modifiedHabit.freqDen);
|
||||
editor.apply();
|
||||
|
||||
Command command = null;
|
||||
Habit savedHabit = null;
|
||||
|
||||
if (mode == EDIT_MODE)
|
||||
{
|
||||
command = originalHabit.new EditCommand(modifiedHabit);
|
||||
command = new EditHabitCommand(originalHabit, modifiedHabit);
|
||||
savedHabit = originalHabit;
|
||||
}
|
||||
|
||||
if (mode == CREATE_MODE) command = new Habit.CreateCommand(modifiedHabit);
|
||||
else if (mode == CREATE_MODE)
|
||||
{
|
||||
command = new CreateHabitCommand(modifiedHabit);
|
||||
}
|
||||
|
||||
if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit);
|
||||
|
||||
@@ -323,9 +349,24 @@ public class EditHabitFragment extends DialogFragment
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,29 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
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;
|
||||
@@ -43,282 +38,107 @@ import android.view.ViewGroup;
|
||||
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.UnarchiveHabitsCommand;
|
||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||
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.loaders.HabitListLoader;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
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<Habit> 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;
|
||||
}
|
||||
}
|
||||
|
||||
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<Integer> 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 = (int) ((width - 160) / 42.0);
|
||||
tvNameWidth = (int) ((width - 30 - buttonCount * 42) * dm.density);
|
||||
|
||||
loader = new HabitListLoader();
|
||||
loader.setListener(this);
|
||||
loader.setCheckmarkCount(buttonCount);
|
||||
|
||||
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
|
||||
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
|
||||
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);
|
||||
|
||||
ProgressBar 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(helper.getButtonCount());
|
||||
loader.setProgressBar(progressBar);
|
||||
|
||||
adapter = new ListHabitsAdapter(getActivity());
|
||||
listView = (DragSortListView) view.findViewById(R.id.listView);
|
||||
llHint.setOnClickListener(this);
|
||||
tvStarEmpty.setTypeface(helper.getFontawesome());
|
||||
|
||||
adapter = new HabitListAdapter(getActivity(), loader);
|
||||
adapter.setSelectedPositions(selectedPositions);
|
||||
adapter.setOnCheckmarkClickListener(this);
|
||||
adapter.setOnCheckmarkLongClickListener(this);
|
||||
|
||||
DragSortListView.DragListener dragListener = new HabitsDragListener();
|
||||
DragSortController dragSortController = new HabitsDragSortController();
|
||||
|
||||
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<>();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -342,42 +162,19 @@ public class ListHabitsFragment extends Fragment
|
||||
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
|
||||
@@ -412,7 +209,7 @@ public class ListHabitsFragment extends Fragment
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "dialog");
|
||||
frag.show(getFragmentManager(), "editHabit");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -471,8 +268,13 @@ public class ListHabitsFragment extends Fragment
|
||||
|
||||
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();
|
||||
@@ -492,12 +294,6 @@ public class ListHabitsFragment extends Fragment
|
||||
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)
|
||||
{
|
||||
@@ -524,16 +320,15 @@ public class ListHabitsFragment extends Fragment
|
||||
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;
|
||||
|
||||
executeCommand(habit.new ToggleRepetitionCommand(timestamp), habit.getId());
|
||||
helper.toggleCheckmarkView(v, habit);
|
||||
executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId());
|
||||
}
|
||||
|
||||
private void executeCommand(Command c, Long refreshKey)
|
||||
@@ -541,43 +336,6 @@ public class ListHabitsFragment extends Fragment
|
||||
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)
|
||||
{
|
||||
@@ -600,179 +358,7 @@ public class ListHabitsFragment extends Fragment
|
||||
break;
|
||||
|
||||
case R.id.llHint:
|
||||
hideHint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class ListHabitsAdapter extends BaseAdapter
|
||||
{
|
||||
private LayoutInflater inflater;
|
||||
private Typeface fontawesome;
|
||||
|
||||
public ListHabitsAdapter(Context context)
|
||||
{
|
||||
|
||||
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return loader.habits.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position)
|
||||
{
|
||||
return loader.habitsList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position)
|
||||
{
|
||||
return ((Habit) 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);
|
||||
((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome);
|
||||
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1);
|
||||
view.findViewById(R.id.tvName).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.tvName);
|
||||
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.setBackgroundColor(Color.WHITE);
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
|
||||
TextView tvCheck = (TextView) llButtons.getChildAt(i);
|
||||
tvCheck.setTag(R.string.habit_key, habitId);
|
||||
tvCheck.setTag(R.string.offset_key, i);
|
||||
updateCheckmark(activeColor, tvCheck, isChecked[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName)
|
||||
{
|
||||
int activeColor = getActiveColor(habit);
|
||||
|
||||
tvName.setText(habit.name);
|
||||
tvName.setTextColor(activeColor);
|
||||
|
||||
if (habit.isArchived())
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_archive));
|
||||
tvStar.setTextColor(activeColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
int score = loader.scores.get(habit.getId());
|
||||
|
||||
if (score < Habit.HALF_STAR_CUTOFF)
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_star_o));
|
||||
tvStar.setTextColor(INACTIVE_COLOR);
|
||||
}
|
||||
else if (score < Habit.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)
|
||||
{
|
||||
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);
|
||||
hintManager.dismissHint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -782,4 +368,51 @@ public class ListHabitsFragment extends Fragment
|
||||
if (refreshKey == null) loader.updateAllHabits(true);
|
||||
else loader.updateHabit(refreshKey);
|
||||
}
|
||||
|
||||
public void onActionModeDestroyed(ActionMode mode)
|
||||
{
|
||||
actionMode = null;
|
||||
selectedPositions.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
listView.setDragEnabled(true);
|
||||
}
|
||||
|
||||
public interface OnHabitClickListener
|
||||
{
|
||||
void onHabitClicked(Habit habit);
|
||||
}
|
||||
|
||||
private class HabitsDragSortController extends DragSortController
|
||||
{
|
||||
public HabitsDragSortController()
|
||||
{
|
||||
super(ListHabitsFragment.this.listView);
|
||||
setRemoveEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateFloatView(int position)
|
||||
{
|
||||
return adapter.getView(position, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyFloatView(View floatView)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class HabitsDragListener implements DragSortListView.DragListener
|
||||
{
|
||||
@Override
|
||||
public void drag(int from, int to)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
selectItem(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
@@ -25,25 +28,34 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
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.helpers.ReminderHelper;
|
||||
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()
|
||||
@@ -59,44 +71,75 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
|
||||
activity = (ShowHabitActivity) getActivity();
|
||||
habit = activity.habit;
|
||||
|
||||
habit.updateCheckmarks();
|
||||
habit.checkmarks.rebuild();
|
||||
|
||||
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);
|
||||
|
||||
updateHeaders(view);
|
||||
updateScoreRing(view);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
tvHistory.setTextColor(habit.color);
|
||||
tvOverview.setTextColor(habit.color);
|
||||
tvStrength.setTextColor(habit.color);
|
||||
tvStreaks.setTextColor(habit.color);
|
||||
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);
|
||||
}
|
||||
|
||||
LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview);
|
||||
llOverview.addView(new RingView(activity,
|
||||
(int) activity.getResources().getDimension(R.dimen.small_square_size) * 4,
|
||||
habit.color, ((float) habit.getScore() / Habit.MAX_SCORE), activity.getString(R.string.habit_strength)));
|
||||
|
||||
LinearLayout llStrength = (LinearLayout) view.findViewById(R.id.llStrength);
|
||||
llStrength.addView(new HabitScoreView(activity, habit,
|
||||
(int) activity.getResources().getDimension(R.dimen.small_square_size)));
|
||||
|
||||
LinearLayout llHistory = (LinearLayout) view.findViewById(R.id.llHistory);
|
||||
HabitHistoryView hhv = new HabitHistoryView(activity, habit,
|
||||
(int) activity.getResources().getDimension(R.dimen.small_square_size));
|
||||
llHistory.addView(hhv);
|
||||
|
||||
LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks);
|
||||
HabitStreakView hsv = new HabitStreakView(activity, habit,
|
||||
(int) activity.getResources().getDimension(R.dimen.small_square_size));
|
||||
llStreaks.addView(hsv);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
private void updateColor(View view, int viewId)
|
||||
{
|
||||
TextView textView = (TextView) view.findViewById(viewId);
|
||||
textView.setTextColor(habit.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +157,7 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "dialog");
|
||||
frag.show(getFragmentManager(), "editHabit");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -133,4 +176,20 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.helpers;
|
||||
@@ -25,7 +28,7 @@ import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.ReminderAlarmReceiver;
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.text.DateFormat;
|
||||
@@ -58,10 +61,10 @@ public class ReminderHelper
|
||||
|
||||
long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime));
|
||||
|
||||
Uri uri = Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", habit.getId()));
|
||||
Uri uri = habit.getUri();
|
||||
|
||||
Intent alarmIntent = new Intent(context, ReminderAlarmReceiver.class);
|
||||
alarmIntent.setAction(ReminderAlarmReceiver.ACTION_REMIND);
|
||||
Intent alarmIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
alarmIntent.setAction(HabitBroadcastReceiver.ACTION_SHOW_REMINDER);
|
||||
alarmIntent.setData(uri);
|
||||
alarmIntent.putExtra("timestamp", timestamp);
|
||||
alarmIntent.putExtra("reminderTime", reminderTime);
|
||||
|
||||
213
app/src/main/java/org/isoron/uhabits/io/CSVExporter.java
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.io;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class CSVExporter
|
||||
{
|
||||
private List<Habit> habits;
|
||||
private Context context;
|
||||
private java.text.DateFormat dateFormat;
|
||||
|
||||
private List<String> generateDirs;
|
||||
private List<String> generateFilenames;
|
||||
|
||||
private String basePath;
|
||||
|
||||
public CSVExporter(Context context, List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
this.context = context;
|
||||
generateDirs = new LinkedList<>();
|
||||
generateFilenames = new LinkedList<>();
|
||||
|
||||
basePath = String.format("%s/export/", context.getFilesDir());
|
||||
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public String formatDate(long timestamp)
|
||||
{
|
||||
return dateFormat.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
public String formatScore(int score)
|
||||
{
|
||||
return String.format("%.2f", ((float) score) / Score.MAX_SCORE);
|
||||
}
|
||||
|
||||
private void writeScores(String dirPath, Habit habit) throws IOException
|
||||
{
|
||||
String path = dirPath + "scores.csv";
|
||||
FileWriter out = new FileWriter(basePath + path);
|
||||
generateFilenames.add(path);
|
||||
|
||||
String query = "select timestamp, score from score where habit = ? order by timestamp";
|
||||
String params[] = { habit.getId().toString() };
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return;
|
||||
|
||||
do
|
||||
{
|
||||
String timestamp = formatDate(cursor.getLong(0));
|
||||
String score = formatScore(cursor.getInt(1));
|
||||
out.write(String.format("%s,%s\n", timestamp, score));
|
||||
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
out.close();
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void writeCheckmarks(String dirPath, Habit habit) throws IOException
|
||||
{
|
||||
String path = dirPath + "checkmarks.csv";
|
||||
FileWriter out = new FileWriter(basePath + path);
|
||||
generateFilenames.add(path);
|
||||
|
||||
String query = "select timestamp, value from checkmarks where habit = ? order by timestamp";
|
||||
String params[] = { habit.getId().toString() };
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return;
|
||||
|
||||
do
|
||||
{
|
||||
String timestamp = formatDate(cursor.getLong(0));
|
||||
Integer value = cursor.getInt(1);
|
||||
out.write(String.format("%s,%d\n", timestamp, value));
|
||||
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
out.close();
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void writeFiles(Habit habit) throws IOException
|
||||
{
|
||||
String path = String.format("%s/", habit.name);
|
||||
new File(basePath + path).mkdirs();
|
||||
generateDirs.add(path);
|
||||
|
||||
writeScores(path, habit);
|
||||
writeCheckmarks(path, habit);
|
||||
}
|
||||
|
||||
private void writeZipFile(String zipFilename) throws IOException
|
||||
{
|
||||
FileOutputStream fos = new FileOutputStream(zipFilename);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
|
||||
for(String filename : generateFilenames)
|
||||
addFileToZip(zos, filename);
|
||||
|
||||
zos.close();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void addFileToZip(ZipOutputStream zos, String filename) throws IOException
|
||||
{
|
||||
FileInputStream fis = new FileInputStream(new File(basePath + filename));
|
||||
ZipEntry ze = new ZipEntry(filename);
|
||||
zos.putNextEntry(ze);
|
||||
|
||||
int length;
|
||||
byte bytes[] = new byte[1024];
|
||||
while((length = fis.read(bytes)) >= 0)
|
||||
zos.write(bytes, 0, length);
|
||||
|
||||
zos.closeEntry();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
private void cleanup()
|
||||
{
|
||||
for(String filename : generateFilenames)
|
||||
new File(basePath + filename).delete();
|
||||
|
||||
for(String filename : generateDirs)
|
||||
new File(basePath + filename).delete();
|
||||
|
||||
new File(basePath).delete();
|
||||
}
|
||||
|
||||
public String writeArchive()
|
||||
{
|
||||
String date = formatDate(DateHelper.getStartOfToday());
|
||||
|
||||
File dir = context.getExternalCacheDir();
|
||||
|
||||
if(dir == null)
|
||||
{
|
||||
Log.e("CSVExporter", "No suitable directory found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String zipFilename = String.format("%s/habits-%s.zip", dir, date);
|
||||
|
||||
try
|
||||
{
|
||||
for (Habit h : habits)
|
||||
writeFiles(h);
|
||||
|
||||
writeZipFile(zipFilename);
|
||||
cleanup();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return zipFilename;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alinson Santos Xavier
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.loaders;
|
||||
@@ -144,8 +144,8 @@ public class HabitListLoader
|
||||
if (isCancelled()) return null;
|
||||
|
||||
Long id = h.getId();
|
||||
newScores.put(id, h.getScore());
|
||||
newCheckmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
|
||||
newScores.put(id, h.scores.getNewestValue());
|
||||
newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
publishProgress(current++, newHabits.size());
|
||||
}
|
||||
@@ -213,8 +213,8 @@ public class HabitListLoader
|
||||
|
||||
Habit h = Habit.get(id);
|
||||
habits.put(id, h);
|
||||
scores.put(id, h.getScore());
|
||||
checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
|
||||
scores.put(id, h.scores.getNewestValue());
|
||||
checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
178
app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CheckmarkList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public CheckmarkList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Checkmark.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public int[] getValues(Long fromTimestamp, Long toTimestamp)
|
||||
{
|
||||
rebuild();
|
||||
|
||||
if(fromTimestamp > toTimestamp) return new int[0];
|
||||
|
||||
String query = "select value, timestamp from Checkmarks where " +
|
||||
"habit = ? and timestamp >= ? and timestamp <= ?";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
String args[] = { habit.getId().toString(), fromTimestamp.toString(),
|
||||
toTimestamp.toString() };
|
||||
Cursor cursor = db.rawQuery(query, args);
|
||||
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
|
||||
int[] checks = new int[nDays];
|
||||
|
||||
if (cursor.moveToFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
long timestamp = cursor.getLong(1);
|
||||
int offset = (int) ((timestamp - fromTimestamp) / day);
|
||||
checks[nDays - offset - 1] = cursor.getInt(0);
|
||||
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
return checks;
|
||||
}
|
||||
|
||||
public int[] getAllValues()
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if(oldestRep == null) return new int[0];
|
||||
|
||||
Long toTimestamp = DateHelper.getStartOfToday();
|
||||
Long fromTimestamp = oldestRep.timestamp;
|
||||
return getValues(fromTimestamp, toTimestamp);
|
||||
}
|
||||
|
||||
public void rebuild()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Checkmark newestCheckmark = getNewest();
|
||||
if (newestCheckmark == null)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginning = newestCheckmark.timestamp + day;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
long beginningExtended = beginning - (long) (habit.freqDen) * day;
|
||||
List<Repetition> reps = habit.repetitions.selectFromTo(beginningExtended, today).execute();
|
||||
|
||||
int nDays = (int) ((today - beginning) / day) + 1;
|
||||
int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
|
||||
|
||||
int checks[] = new int[nDaysExtended];
|
||||
|
||||
// explicit checks
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
int offset = (int) ((rep.timestamp - beginningExtended) / day);
|
||||
checks[nDaysExtended - offset - 1] = 2;
|
||||
}
|
||||
|
||||
// implicit checks
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
for (int j = 0; j < habit.freqDen; j++)
|
||||
if (checks[i + j] == 2) counter++;
|
||||
|
||||
if (counter >= habit.freqNum) checks[i] = Math.max(checks[i], 1);
|
||||
}
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
Checkmark c = new Checkmark();
|
||||
c.habit = habit;
|
||||
c.timestamp = today - i * day;
|
||||
c.value = checks[i];
|
||||
c.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Checkmark getNewest()
|
||||
{
|
||||
return new Select().from(Checkmark.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public int getCurrentValue()
|
||||
{
|
||||
rebuild();
|
||||
Checkmark c = getNewest();
|
||||
|
||||
if(c != null) return c.value;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
@@ -32,21 +33,12 @@ import com.activeandroid.query.Update;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "Habits")
|
||||
public class Habit extends Model
|
||||
{
|
||||
|
||||
public static final int HALF_STAR_CUTOFF = 9629750;
|
||||
public static final int FULL_STAR_CUTOFF = 15407600;
|
||||
public static final int MAX_SCORE = 19259500;
|
||||
|
||||
@Column(name = "name")
|
||||
public String name;
|
||||
|
||||
@@ -80,20 +72,35 @@ public class Habit extends Model
|
||||
@Column(name = "archived")
|
||||
public Integer archived;
|
||||
|
||||
public StreakList streaks;
|
||||
public ScoreList scores;
|
||||
public RepetitionList repetitions;
|
||||
public CheckmarkList checkmarks;
|
||||
|
||||
public Habit(Habit model)
|
||||
{
|
||||
copyAttributes(model);
|
||||
initializeLists();
|
||||
}
|
||||
|
||||
public Habit()
|
||||
{
|
||||
this.color = ColorHelper.palette[5];
|
||||
this.position = Habit.getCount();
|
||||
this.position = Habit.countWithArchived();
|
||||
this.highlight = 0;
|
||||
this.archived = 0;
|
||||
this.freqDen = 7;
|
||||
this.freqNum = 3;
|
||||
this.reminderDays = 127;
|
||||
initializeLists();
|
||||
}
|
||||
|
||||
private void initializeLists()
|
||||
{
|
||||
streaks = new StreakList(this);
|
||||
scores = new ScoreList(this);
|
||||
repetitions = new RepetitionList(this);
|
||||
checkmarks = new CheckmarkList(this);
|
||||
}
|
||||
|
||||
public static Habit get(Long id)
|
||||
@@ -123,11 +130,16 @@ public class Habit extends Model
|
||||
return new Select().from(Habit.class).orderBy("position");
|
||||
}
|
||||
|
||||
public static int getCount()
|
||||
public static int count()
|
||||
{
|
||||
return select().count();
|
||||
}
|
||||
|
||||
public static int countWithArchived()
|
||||
{
|
||||
return selectWithArchived().count();
|
||||
}
|
||||
|
||||
public static java.util.List<Habit> getHighlightedHabits()
|
||||
{
|
||||
return select().where("highlight = 1")
|
||||
@@ -176,7 +188,8 @@ public class Habit extends Model
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
@@ -204,22 +217,6 @@ public class Habit extends Model
|
||||
Habit.updateId(getId(), id);
|
||||
}
|
||||
|
||||
protected From selectReps()
|
||||
{
|
||||
return new Select().from(Repetition.class).where("habit = ?", getId()).orderBy("timestamp");
|
||||
}
|
||||
|
||||
protected From selectRepsFromTo(long timeFrom, long timeTo)
|
||||
{
|
||||
return selectReps().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
|
||||
}
|
||||
|
||||
public boolean hasRep(long timestamp)
|
||||
{
|
||||
int count = selectReps().where("timestamp = ?", timestamp).count();
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
public void cascadeDelete()
|
||||
{
|
||||
Long id = getId();
|
||||
@@ -241,176 +238,9 @@ public class Habit extends Model
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteReps(long timestamp)
|
||||
public Uri getUri()
|
||||
{
|
||||
new Delete().from(Repetition.class)
|
||||
.where("habit = ?", getId())
|
||||
.and("timestamp = ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public void deleteCheckmarksNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Checkmark.class)
|
||||
.where("habit = ?", getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public void deleteStreaksNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Streak.class)
|
||||
.where("habit = ?", getId())
|
||||
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public int[] getCheckmarks(Long fromTimestamp, Long toTimestamp)
|
||||
{
|
||||
updateCheckmarks();
|
||||
|
||||
String query = "select value, timestamp from Checkmarks where " +
|
||||
"habit = ? and timestamp >= ? and timestamp <= ?";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
String args[] = {getId().toString(), fromTimestamp.toString(), toTimestamp.toString()};
|
||||
Cursor cursor = db.rawQuery(query, args);
|
||||
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
|
||||
int[] checks = new int[nDays];
|
||||
|
||||
if (cursor.moveToFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
long timestamp = cursor.getLong(1);
|
||||
int offset = (int) ((timestamp - fromTimestamp) / day);
|
||||
checks[nDays - offset - 1] = cursor.getInt(0);
|
||||
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
return checks;
|
||||
}
|
||||
|
||||
public void updateCheckmarks()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Checkmark newestCheckmark = getNewestCheckmark();
|
||||
if (newestCheckmark == null)
|
||||
{
|
||||
Repetition oldestRep = getOldestRep();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginning = newestCheckmark.timestamp + day;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
long beginningExtended = beginning - (long) (freqDen) * day;
|
||||
List<Repetition> reps = selectRepsFromTo(beginningExtended, today).execute();
|
||||
|
||||
int nDays = (int) ((today - beginning) / day) + 1;
|
||||
int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
|
||||
|
||||
int checks[] = new int[nDaysExtended];
|
||||
|
||||
// explicit checks
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
int offset = (int) ((rep.timestamp - beginningExtended) / day);
|
||||
checks[nDaysExtended - offset - 1] = 2;
|
||||
}
|
||||
|
||||
// implicit checks
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
for (int j = 0; j < freqDen; j++)
|
||||
if (checks[i + j] == 2) counter++;
|
||||
|
||||
if (counter >= freqNum) checks[i] = Math.max(checks[i], 1);
|
||||
}
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
Checkmark c = new Checkmark();
|
||||
c.habit = this;
|
||||
c.timestamp = today - i * day;
|
||||
c.value = checks[i];
|
||||
c.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Checkmark getNewestCheckmark()
|
||||
{
|
||||
return new Select().from(Checkmark.class)
|
||||
.where("habit = ?", getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public int getRepsCount(int days)
|
||||
{
|
||||
long timeTo = DateHelper.getStartOfToday();
|
||||
long timeFrom = timeTo - DateHelper.millisecondsInOneDay * days;
|
||||
return selectRepsFromTo(timeFrom, timeTo).count();
|
||||
}
|
||||
|
||||
public boolean hasImplicitRepToday()
|
||||
{
|
||||
long today = DateHelper.getStartOfToday();
|
||||
int reps[] = getCheckmarks(today - DateHelper.millisecondsInOneDay, today);
|
||||
return (reps[0] > 0);
|
||||
}
|
||||
|
||||
public Repetition getOldestRep()
|
||||
{
|
||||
return (Repetition) selectReps().limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public Repetition getOldestRepNewerThan(long timestamp)
|
||||
{
|
||||
return selectReps().where("timestamp > ?", timestamp).limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public void toggleRepetition(long timestamp)
|
||||
{
|
||||
if (hasRep(timestamp))
|
||||
{
|
||||
deleteReps(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition rep = new Repetition();
|
||||
rep.habit = this;
|
||||
rep.timestamp = timestamp;
|
||||
rep.save();
|
||||
}
|
||||
|
||||
deleteScoresNewerThan(timestamp);
|
||||
deleteCheckmarksNewerThan(timestamp);
|
||||
deleteStreaksNewerThan(timestamp);
|
||||
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
|
||||
}
|
||||
|
||||
public void archive()
|
||||
@@ -429,297 +259,4 @@ public class Habit extends Model
|
||||
{
|
||||
return archived != 0;
|
||||
}
|
||||
|
||||
public void toggleRepetitionToday()
|
||||
{
|
||||
toggleRepetition(DateHelper.getStartOfToday());
|
||||
}
|
||||
|
||||
public Score getNewestScore()
|
||||
{
|
||||
return new Select().from(Score.class)
|
||||
.where("habit = ?", getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void deleteScoresNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Score.class)
|
||||
.where("habit = ?", getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Integer getScore()
|
||||
{
|
||||
int beginningScore;
|
||||
long beginningTime;
|
||||
|
||||
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
double freq = ((double) freqNum) / freqDen;
|
||||
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
|
||||
|
||||
Score newestScore = getNewestScore();
|
||||
if (newestScore == null)
|
||||
{
|
||||
Repetition oldestRep = getOldestRep();
|
||||
if (oldestRep == null) return 0;
|
||||
beginningTime = oldestRep.timestamp;
|
||||
beginningScore = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginningTime = newestScore.timestamp + day;
|
||||
beginningScore = newestScore.score;
|
||||
}
|
||||
|
||||
long nDays = (today - beginningTime) / day;
|
||||
if (nDays < 0) return newestScore.score;
|
||||
|
||||
int reps[] = getCheckmarks(beginningTime, today);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
int lastScore = beginningScore;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < reps.length; i++)
|
||||
{
|
||||
Score s = new Score();
|
||||
s.habit = this;
|
||||
s.timestamp = beginningTime + day * i;
|
||||
s.score = (int) (lastScore * multiplier);
|
||||
if (reps[reps.length - i - 1] == 2)
|
||||
{
|
||||
s.score += 1000000;
|
||||
s.score = Math.min(s.score, MAX_SCORE);
|
||||
}
|
||||
s.save();
|
||||
|
||||
lastScore = s.score;
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
|
||||
return lastScore;
|
||||
}
|
||||
|
||||
public List<Score> getScores(long fromTimestamp, long toTimestamp, int divisor, long offset)
|
||||
{
|
||||
return new Select().from(Score.class)
|
||||
.where("habit = ? and timestamp > ? and " +
|
||||
"timestamp <= ? and (timestamp - ?) % ? = 0", getId(), fromTimestamp,
|
||||
toTimestamp, offset, divisor)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public List<Streak> getStreaks()
|
||||
{
|
||||
updateStreaks();
|
||||
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", getId())
|
||||
.orderBy("end asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Streak getNewestStreak()
|
||||
{
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", getId())
|
||||
.orderBy("end desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void updateStreaks()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Streak newestStreak = getNewestStreak();
|
||||
if (newestStreak == null)
|
||||
{
|
||||
Repetition oldestRep = getOldestRep();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition oldestRep = getOldestRepNewerThan(newestStreak.end);
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
int checks[] = getCheckmarks(beginning, today);
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
|
||||
long current = beginning;
|
||||
list.add(current);
|
||||
|
||||
for (int i = 1; i < checks.length; i++)
|
||||
{
|
||||
current += day;
|
||||
int j = checks.length - i - 1;
|
||||
|
||||
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
|
||||
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
|
||||
}
|
||||
|
||||
if (list.size() % 2 == 1) list.add(current);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < list.size(); i += 2)
|
||||
{
|
||||
Streak streak = new Streak();
|
||||
streak.habit = this;
|
||||
streak.start = list.get(i);
|
||||
streak.end = list.get(i + 1);
|
||||
streak.length = (streak.end - streak.start) / day + 1;
|
||||
streak.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public static class CreateCommand extends Command
|
||||
{
|
||||
private Habit model;
|
||||
private Long savedId;
|
||||
|
||||
public CreateCommand(Habit model)
|
||||
{
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit savedHabit = new Habit(model);
|
||||
if (savedId == null)
|
||||
{
|
||||
savedHabit.save();
|
||||
savedId = savedHabit.getId();
|
||||
}
|
||||
else
|
||||
{
|
||||
savedHabit.save(savedId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
Habit.get(savedId).delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class EditCommand extends Command
|
||||
{
|
||||
private Habit original;
|
||||
private Habit modified;
|
||||
private long savedId;
|
||||
private boolean hasIntervalChanged;
|
||||
|
||||
public EditCommand(Habit modified)
|
||||
{
|
||||
this.savedId = getId();
|
||||
this.modified = new Habit(modified);
|
||||
this.original = new Habit(Habit.this);
|
||||
|
||||
hasIntervalChanged = (this.original.freqDen != this.modified.freqDen ||
|
||||
this.original.freqNum != this.modified.freqNum);
|
||||
}
|
||||
|
||||
public void execute()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(modified);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.deleteCheckmarksNewerThan(0);
|
||||
habit.deleteStreaksNewerThan(0);
|
||||
habit.deleteScoresNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void undo()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(original);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.deleteCheckmarksNewerThan(0);
|
||||
habit.deleteStreaksNewerThan(0);
|
||||
habit.deleteScoresNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed_back;
|
||||
}
|
||||
}
|
||||
|
||||
public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
private Long offset;
|
||||
|
||||
public ToggleRepetitionCommand(long offset)
|
||||
{
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
toggleRepetition(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
159
app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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
|
||||
{
|
||||
|
||||
private Habit habit;
|
||||
|
||||
public RepetitionList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
protected From select()
|
||||
{
|
||||
return new Select().from(Repetition.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp");
|
||||
}
|
||||
|
||||
protected From selectFromTo(long timeFrom, long timeTo)
|
||||
{
|
||||
return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
|
||||
}
|
||||
|
||||
public boolean contains(long timestamp)
|
||||
{
|
||||
int count = select().where("timestamp = ?", timestamp).count();
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
public void delete(long timestamp)
|
||||
{
|
||||
new Delete().from(Repetition.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp = ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Repetition getOldestNewerThan(long timestamp)
|
||||
{
|
||||
return select().where("timestamp > ?", timestamp).limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public void toggle(long timestamp)
|
||||
{
|
||||
timestamp = DateHelper.getStartOfDay(timestamp);
|
||||
|
||||
if (contains(timestamp))
|
||||
{
|
||||
delete(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition rep = new Repetition();
|
||||
rep.habit = habit;
|
||||
rep.timestamp = timestamp;
|
||||
rep.save();
|
||||
}
|
||||
|
||||
habit.scores.deleteNewerThan(timestamp);
|
||||
habit.checkmarks.deleteNewerThan(timestamp);
|
||||
habit.streaks.deleteNewerThan(timestamp);
|
||||
}
|
||||
|
||||
public Repetition getOldest()
|
||||
{
|
||||
return (Repetition) select().limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public boolean hasImplicitRepToday()
|
||||
{
|
||||
long today = DateHelper.getStartOfToday();
|
||||
int reps[] = habit.checkmarks.getValues(today - DateHelper.millisecondsInOneDay, today);
|
||||
return (reps[0] > 0);
|
||||
}
|
||||
|
||||
public HashMap<Long, Integer[]> 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 <Long, Integer[]> 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;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
@@ -23,6 +26,10 @@ import com.activeandroid.annotation.Table;
|
||||
@Table(name = "Score")
|
||||
public class Score extends Model
|
||||
{
|
||||
public static final int HALF_STAR_CUTOFF = 9629750;
|
||||
public static final int FULL_STAR_CUTOFF = 15407600;
|
||||
public static final int MAX_SCORE = 19259500;
|
||||
|
||||
@Column(name = "habit")
|
||||
public Habit habit;
|
||||
|
||||
|
||||
169
app/src/main/java/org/isoron/uhabits/models/ScoreList.java
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
public class ScoreList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public ScoreList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public int getCurrentStarStatus()
|
||||
{
|
||||
int score = getNewestValue();
|
||||
|
||||
if(score >= Score.FULL_STAR_CUTOFF) return 2;
|
||||
else if(score >= Score.HALF_STAR_CUTOFF) return 1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public Score getNewest()
|
||||
{
|
||||
return new Select().from(Score.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Score.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Integer getNewestValue()
|
||||
{
|
||||
int beginningScore;
|
||||
long beginningTime;
|
||||
|
||||
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
double freq = ((double) habit.freqNum) / habit.freqDen;
|
||||
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
|
||||
|
||||
Score newestScore = getNewest();
|
||||
if (newestScore == null)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return 0;
|
||||
beginningTime = oldestRep.timestamp;
|
||||
beginningScore = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginningTime = newestScore.timestamp + day;
|
||||
beginningScore = newestScore.score;
|
||||
}
|
||||
|
||||
long nDays = (today - beginningTime) / day;
|
||||
if (nDays < 0) return newestScore.score;
|
||||
|
||||
int reps[] = habit.checkmarks.getValues(beginningTime, today);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
int lastScore = beginningScore;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < reps.length; i++)
|
||||
{
|
||||
Score s = new Score();
|
||||
s.habit = habit;
|
||||
s.timestamp = beginningTime + day * i;
|
||||
s.score = (int) (lastScore * multiplier);
|
||||
if (reps[reps.length - i - 1] == 2)
|
||||
{
|
||||
s.score += 1000000;
|
||||
s.score = Math.min(s.score, Score.MAX_SCORE);
|
||||
}
|
||||
s.save();
|
||||
|
||||
lastScore = s.score;
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
|
||||
return lastScore;
|
||||
}
|
||||
|
||||
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 " +
|
||||
"where habit = ? and timestamp > ? and timestamp <= ? " +
|
||||
"group by time order by time desc";
|
||||
|
||||
String params[] = { offset.toString(), divisor.toString(), habit.getId().toString(),
|
||||
fromTimestamp.toString(), toTimestamp.toString()};
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return new int[0];
|
||||
|
||||
int k = 0;
|
||||
int[] scores = new int[cursor.getCount()];
|
||||
|
||||
do
|
||||
{
|
||||
scores[k++] = (int) cursor.getLong(1);
|
||||
}
|
||||
while (cursor.moveToNext());
|
||||
|
||||
cursor.close();
|
||||
return scores;
|
||||
|
||||
}
|
||||
|
||||
public int[] getAllValues(int divisor)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if(oldestRep == null) return new int[0];
|
||||
|
||||
long fromTimestamp = oldestRep.timestamp;
|
||||
long toTimestamp = DateHelper.getStartOfToday();
|
||||
return getAllValues(fromTimestamp, toTimestamp, divisor);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
129
app/src/main/java/org/isoron/uhabits/models/StreakList.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StreakList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public StreakList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public List<Streak> getAll()
|
||||
{
|
||||
rebuild();
|
||||
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("end asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Streak getNewest()
|
||||
{
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("end desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void rebuild()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Streak newestStreak = getNewest();
|
||||
if (newestStreak != null)
|
||||
{
|
||||
beginning = newestStreak.start;
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
int checks[] = habit.checkmarks.getValues(beginning, today);
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
|
||||
long current = beginning;
|
||||
list.add(current);
|
||||
|
||||
for (int i = 1; i < checks.length; i++)
|
||||
{
|
||||
current += day;
|
||||
int j = checks.length - i - 1;
|
||||
|
||||
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
|
||||
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
|
||||
}
|
||||
|
||||
if (list.size() % 2 == 1) list.add(current);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
if(newestStreak != null) newestStreak.delete();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < list.size(); i += 2)
|
||||
{
|
||||
Streak streak = new Streak();
|
||||
streak.habit = habit;
|
||||
streak.start = list.get(i);
|
||||
streak.end = list.get(i + 1);
|
||||
streak.length = (streak.end - streak.start) / day + 1;
|
||||
streak.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
206
app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class CheckmarkView extends View
|
||||
{
|
||||
private Paint pCard;
|
||||
private Paint pIcon;
|
||||
|
||||
private int primaryColor;
|
||||
private int backgroundColor;
|
||||
private int timesColor;
|
||||
private int darkGrey;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private int leftMargin;
|
||||
private int topMargin;
|
||||
private int padding;
|
||||
private String label;
|
||||
|
||||
private String fa_check;
|
||||
private String fa_times;
|
||||
private String fa_full_star;
|
||||
private String fa_half_star;
|
||||
private String fa_empty_star;
|
||||
|
||||
private int check_status;
|
||||
private int star_status;
|
||||
|
||||
private Rect rect;
|
||||
private TextPaint textPaint;
|
||||
private StaticLayout labelLayout;
|
||||
|
||||
public CheckmarkView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckmarkView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context)
|
||||
{
|
||||
Typeface fontawesome =
|
||||
Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
||||
|
||||
pCard = new Paint();
|
||||
pCard.setAntiAlias(true);
|
||||
|
||||
pIcon = new Paint();
|
||||
pIcon.setAntiAlias(true);
|
||||
pIcon.setTypeface(fontawesome);
|
||||
pIcon.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
textPaint = new TextPaint();
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setAntiAlias(true);
|
||||
|
||||
fa_check = context.getString(R.string.fa_check);
|
||||
fa_times = context.getString(R.string.fa_times);
|
||||
fa_empty_star = context.getString(R.string.fa_star_o);
|
||||
fa_half_star = context.getString(R.string.fa_star_half_o);
|
||||
fa_full_star = context.getString(R.string.fa_star);
|
||||
|
||||
primaryColor = ColorHelper.palette[10];
|
||||
backgroundColor = Color.argb(255, 255, 255, 255);
|
||||
timesColor = Color.argb(128, 255, 255, 255);
|
||||
darkGrey = Color.argb(64, 0, 0, 0);
|
||||
|
||||
rect = new Rect();
|
||||
check_status = 2;
|
||||
star_status = 0;
|
||||
label = "Wake up early";
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.check_status = habit.checkmarks.getCurrentValue();
|
||||
this.star_status = habit.scores.getCurrentStarStatus();
|
||||
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color), Color.blue(habit.color));
|
||||
this.label = habit.name;
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawBackground(canvas);
|
||||
drawCheckmark(canvas);
|
||||
drawLabel(canvas);
|
||||
}
|
||||
|
||||
private void drawBackground(Canvas canvas)
|
||||
{
|
||||
int color = (check_status == 2 ? primaryColor : darkGrey);
|
||||
|
||||
pCard.setColor(color);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
canvas.drawRoundRect(leftMargin, topMargin, width - leftMargin, height - topMargin, padding,
|
||||
padding, pCard);
|
||||
else
|
||||
canvas.drawRect(leftMargin, topMargin, width - leftMargin, height - topMargin, pCard);
|
||||
}
|
||||
|
||||
private void drawCheckmark(Canvas canvas)
|
||||
{
|
||||
String text = (check_status == 0 ? fa_times : fa_check);
|
||||
int color = (check_status == 2 ? Color.WHITE : timesColor);
|
||||
|
||||
pIcon.setColor(color);
|
||||
pIcon.setTextSize(width * 0.5f);
|
||||
pIcon.getTextBounds(text, 0, 1, rect);
|
||||
|
||||
// canvas.drawLine(0, 0.67f * height, width, 0.67f * height, pIcon);
|
||||
|
||||
int y = (int) ((0.67f * height - rect.bottom - rect.top) / 2);
|
||||
canvas.drawText(text, width / 2, y, pIcon);
|
||||
}
|
||||
|
||||
private void drawLabel(Canvas canvas)
|
||||
{
|
||||
canvas.save();
|
||||
float y;
|
||||
int nLines = labelLayout.getLineCount();
|
||||
|
||||
if(nLines == 1)
|
||||
y = height * 0.8f - padding;
|
||||
else
|
||||
y = height * 0.7f - padding;
|
||||
|
||||
canvas.translate(leftMargin + padding, y);
|
||||
|
||||
labelLayout.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
setMeasuredDimension(width, (int) (width * 1.25));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
this.width = getMeasuredWidth();
|
||||
this.height = getMeasuredHeight();
|
||||
|
||||
leftMargin = (int) (width * 0.015);
|
||||
topMargin = (int) (height * 0.015);
|
||||
padding = 8 * leftMargin;
|
||||
textPaint.setTextSize(0.15f * width);
|
||||
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
private void updateLabel()
|
||||
{
|
||||
textPaint.setColor(Color.WHITE);
|
||||
labelLayout = new StaticLayout(label, textPaint, width - 2 * leftMargin - 2 * padding,
|
||||
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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<Long, Integer[]> 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();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
@@ -22,18 +25,23 @@ import android.graphics.Color;
|
||||
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;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitHistoryView extends ScrollableDataView
|
||||
{
|
||||
|
||||
private Habit habit;
|
||||
private int[] checkmarks;
|
||||
private Paint pSquareBg, pSquareFg, pTextHeader;
|
||||
@@ -42,6 +50,10 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
private float squareTextOffset;
|
||||
private float headerTextOffset;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int nColumns;
|
||||
|
||||
private String wdays[];
|
||||
private SimpleDateFormat dfMonth;
|
||||
private SimpleDateFormat dfYear;
|
||||
@@ -50,25 +62,48 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
private int nDays;
|
||||
private int todayWeekday;
|
||||
private int colors[];
|
||||
private Rect baseLocation;
|
||||
private int primaryColor;
|
||||
|
||||
public HabitHistoryView(Context context, Habit habit, int baseSize)
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private boolean isEditable;
|
||||
|
||||
public HabitHistoryView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context);
|
||||
this.habit = habit;
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
this.checkmarks = new int[0];
|
||||
this.isEditable = false;
|
||||
init();
|
||||
}
|
||||
|
||||
setDimensions(baseSize);
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
createColors();
|
||||
refreshData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
refreshData();
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
wdays = DateHelper.getShortDayNames();
|
||||
dfMonth = new SimpleDateFormat("MMM");
|
||||
dfYear = new SimpleDateFormat("yyyy");
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
|
||||
|
||||
baseLocation = new Rect();
|
||||
}
|
||||
|
||||
private void updateDate()
|
||||
{
|
||||
baseDate = new GregorianCalendar();
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -(dataOffset - 1) * 7);
|
||||
baseDate.setTimeInMillis(DateHelper.getLocalTime());
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
|
||||
|
||||
nDays = (nColumns - 1) * 7;
|
||||
todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
|
||||
@@ -78,71 +113,129 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh)
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
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 < 8) height = 200;
|
||||
int baseSize = height / 8;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
squareSpacing = (int) Math.floor(baseSize / 15.0);
|
||||
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()
|
||||
{
|
||||
int primaryColor = habit.color;
|
||||
int primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f);
|
||||
int grey = Color.rgb(230, 230, 230);
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
colors = new int[3];
|
||||
colors[0] = grey;
|
||||
colors[1] = primaryColorBright;
|
||||
colors[2] = primaryColor;
|
||||
}
|
||||
if(isBackgroundTransparent)
|
||||
primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f);
|
||||
|
||||
private void setDimensions(int baseSize)
|
||||
int red = Color.red(primaryColor);
|
||||
int green = Color.green(primaryColor);
|
||||
int blue = Color.blue(primaryColor);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
squareSpacing = 2;
|
||||
colors = new int[3];
|
||||
colors[0] = Color.argb(16, 255, 255, 255);
|
||||
colors[1] = Color.argb(128, red, green, blue);
|
||||
colors[2] = primaryColor;
|
||||
textColor = Color.rgb(255, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
colors = new int[3];
|
||||
colors[0] = Color.argb(25, 0, 0, 0);
|
||||
colors[1] = Color.argb(127, red, green, blue);
|
||||
colors[2] = primaryColor;
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void createPaints()
|
||||
protected void createPaints()
|
||||
{
|
||||
pTextHeader = new Paint();
|
||||
pTextHeader.setColor(Color.LTGRAY);
|
||||
pTextHeader.setTextAlign(Align.LEFT);
|
||||
pTextHeader.setTextSize(columnWidth * 0.5f);
|
||||
pTextHeader.setAntiAlias(true);
|
||||
|
||||
pSquareBg = new Paint();
|
||||
pSquareBg.setColor(habit.color);
|
||||
pSquareBg.setColor(primaryColor);
|
||||
|
||||
pSquareFg = new Paint();
|
||||
pSquareFg.setColor(Color.WHITE);
|
||||
pSquareFg.setAntiAlias(true);
|
||||
pSquareFg.setTextSize(columnWidth * 0.5f);
|
||||
pSquareFg.setTextAlign(Align.CENTER);
|
||||
|
||||
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
|
||||
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
|
||||
}
|
||||
|
||||
protected void fetchData()
|
||||
public void refreshData()
|
||||
{
|
||||
Calendar currentDate = new GregorianCalendar();
|
||||
currentDate.add(Calendar.DAY_OF_YEAR, -dataOffset * 7);
|
||||
int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7;
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if(habit == null) return;
|
||||
checkmarks = habit.checkmarks.getAllValues();
|
||||
}
|
||||
|
||||
long dateTo = DateHelper.getStartOfToday();
|
||||
for (int i = 0; i < 7 - dayOfWeek; i++)
|
||||
dateTo += DateHelper.millisecondsInOneDay;
|
||||
|
||||
for (int i = 0; i < dataOffset * 7; i++)
|
||||
dateTo -= DateHelper.millisecondsInOneDay;
|
||||
|
||||
long dateFrom = dateTo;
|
||||
for (int i = 0; i < (nColumns - 1) * 7; i++)
|
||||
dateFrom -= DateHelper.millisecondsInOneDay;
|
||||
|
||||
checkmarks = habit.getCheckmarks(dateFrom, dateTo);
|
||||
updateDate();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
Random random = new Random();
|
||||
checkmarks = new int[100];
|
||||
|
||||
for(int i = 0; i < 100; i++)
|
||||
if(random.nextFloat() < 0.3) checkmarks[i] = 2;
|
||||
|
||||
for(int i = 0; i < 100 - 7; i++)
|
||||
{
|
||||
int count = 0;
|
||||
for (int j = 0; j < 7; j++)
|
||||
if(checkmarks[i + j] != 0)
|
||||
count++;
|
||||
|
||||
if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
|
||||
}
|
||||
}
|
||||
|
||||
private String previousMonth;
|
||||
@@ -154,21 +247,25 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
Rect location = new Rect(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
|
||||
baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
|
||||
baseLocation.offset(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
previousMonth = "";
|
||||
previousYear = "";
|
||||
justPrintedYear = false;
|
||||
|
||||
pTextHeader.setColor(textColor);
|
||||
|
||||
updateDate();
|
||||
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
|
||||
|
||||
for (int column = 0; column < nColumns - 1; column++)
|
||||
{
|
||||
drawColumn(canvas, location, currentDate, column);
|
||||
location.offset(columnWidth, -columnHeight);
|
||||
drawColumn(canvas, baseLocation, currentDate, column);
|
||||
baseLocation.offset(columnWidth, - columnHeight);
|
||||
}
|
||||
|
||||
drawAxis(canvas, location);
|
||||
drawAxis(canvas, baseLocation);
|
||||
}
|
||||
|
||||
private void drawColumn(Canvas canvas, Rect location, GregorianCalendar date, int column)
|
||||
@@ -178,9 +275,9 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
|
||||
for (int j = 0; j < 7; j++)
|
||||
{
|
||||
if (!(column == nColumns - 2 && dataOffset == 0 && j > todayWeekday))
|
||||
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayWeekday))
|
||||
{
|
||||
int checkmarkOffset = nDays - 7 * column - j;
|
||||
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
|
||||
drawSquare(canvas, location, date, checkmarkOffset);
|
||||
}
|
||||
|
||||
@@ -210,6 +307,8 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
}
|
||||
}
|
||||
|
||||
private boolean justSkippedColumn = false;
|
||||
|
||||
private void drawColumnHeader(Canvas canvas, Rect location, GregorianCalendar date)
|
||||
{
|
||||
String month = dfMonth.format(date.getTime());
|
||||
@@ -218,22 +317,94 @@ public class HabitHistoryView extends ScrollableDataView
|
||||
if (!month.equals(previousMonth))
|
||||
{
|
||||
int offset = 0;
|
||||
if (justPrintedYear) offset += columnWidth;
|
||||
if (justPrintedYear)
|
||||
{
|
||||
offset += columnWidth;
|
||||
justSkippedColumn = true;
|
||||
}
|
||||
|
||||
canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset,
|
||||
pTextHeader);
|
||||
|
||||
previousMonth = month;
|
||||
justPrintedYear = false;
|
||||
}
|
||||
else if (!year.equals(previousYear))
|
||||
{
|
||||
if(!justSkippedColumn)
|
||||
{
|
||||
canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
|
||||
previousYear = year;
|
||||
justPrintedYear = true;
|
||||
}
|
||||
|
||||
justSkippedColumn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
justSkippedColumn = false;
|
||||
justPrintedYear = false;
|
||||
}
|
||||
}
|
||||
|
||||
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<Void, Void, Void>()
|
||||
{
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
@@ -20,7 +23,10 @@ import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
@@ -28,71 +34,161 @@ import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitScoreView extends ScrollableDataView
|
||||
{
|
||||
public static final int BUCKET_SIZE = 7;
|
||||
public static final PorterDuffXfermode XFERMODE_CLEAR =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
||||
public static final PorterDuffXfermode XFERMODE_SRC =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
||||
|
||||
private final Paint pGrid;
|
||||
private final float em;
|
||||
private Paint pGrid;
|
||||
private float em;
|
||||
private Habit habit;
|
||||
private SimpleDateFormat dfMonth;
|
||||
private SimpleDateFormat dfDay;
|
||||
|
||||
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 List<Score> scores;
|
||||
private int[] scores;
|
||||
private int primaryColor;
|
||||
private boolean isBackgroundTransparent;
|
||||
|
||||
public HabitScoreView(Context context, Habit habit, int columnWidth)
|
||||
public HabitScoreView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
this.scores = new int[0];
|
||||
init();
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
super(context);
|
||||
this.habit = habit;
|
||||
createColors();
|
||||
refreshData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
pText = new Paint();
|
||||
pText.setColor(Color.LTGRAY);
|
||||
pText.setTextAlign(Paint.Align.LEFT);
|
||||
pText.setTextSize(columnWidth * 0.5f);
|
||||
pText.setAntiAlias(true);
|
||||
private void init()
|
||||
{
|
||||
refreshData();
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
pGraph = new Paint();
|
||||
pGraph.setTextAlign(Paint.Align.CENTER);
|
||||
pGraph.setTextSize(columnWidth * 0.5f);
|
||||
pGraph.setAntiAlias(true);
|
||||
pGraph.setStrokeWidth(columnWidth * 0.1f);
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
dfDay = new SimpleDateFormat("d", Locale.getDefault());
|
||||
|
||||
pGrid = new Paint();
|
||||
pGrid.setColor(Color.LTGRAY);
|
||||
pGrid.setAntiAlias(true);
|
||||
pGrid.setStrokeWidth(columnWidth * 0.05f);
|
||||
rect = new RectF();
|
||||
prevRect = new RectF();
|
||||
}
|
||||
|
||||
this.columnWidth = columnWidth;
|
||||
columnHeight = 8 * columnWidth;
|
||||
headerHeight = columnWidth;
|
||||
footerHeight = columnWidth;
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
em = pText.getFontSpacing();
|
||||
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] = habit.color;
|
||||
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 fetchData()
|
||||
protected void createPaints()
|
||||
{
|
||||
pText = new Paint();
|
||||
pText.setAntiAlias(true);
|
||||
|
||||
long toTimestamp = DateHelper.getStartOfToday();
|
||||
for (int i = 0; i < dataOffset * BUCKET_SIZE; i++)
|
||||
toTimestamp -= DateHelper.millisecondsInOneDay;
|
||||
pGraph = new Paint();
|
||||
pGraph.setTextAlign(Paint.Align.CENTER);
|
||||
pGraph.setAntiAlias(true);
|
||||
|
||||
long fromTimestamp = toTimestamp;
|
||||
for (int i = 0; i < nColumns * BUCKET_SIZE; i++)
|
||||
fromTimestamp -= DateHelper.millisecondsInOneDay;
|
||||
pGrid = new Paint();
|
||||
pGrid.setAntiAlias(true);
|
||||
}
|
||||
|
||||
scores = habit.getScores(fromTimestamp, toTimestamp,
|
||||
BUCKET_SIZE * DateHelper.millisecondsInOneDay, toTimestamp);
|
||||
@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 / 9;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
nColumns = width / baseSize;
|
||||
paddingTop = (int) (baseSize * 0.15f);
|
||||
|
||||
pText.setTextSize(baseSize * 0.5f);
|
||||
pGraph.setTextSize(baseSize * 0.5f);
|
||||
pGraph.setStrokeWidth(baseSize * 0.1f);
|
||||
pGrid.setStrokeWidth(baseSize * 0.05f);
|
||||
em = pText.getFontSpacing();
|
||||
}
|
||||
|
||||
public void refreshData()
|
||||
{
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if (habit == null) return;
|
||||
scores = habit.scores.getAllValues(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
Random random = new Random();
|
||||
scores = new int[100];
|
||||
scores[0] = Score.MAX_SCORE / 2;
|
||||
|
||||
for(int i = 1; i < 100; i++)
|
||||
{
|
||||
int step = Score.MAX_SCORE / 10;
|
||||
scores[i] = scores[i - 1] + random.nextInt(step * 2) - step;
|
||||
scores[i] = Math.max(0, Math.min(Score.MAX_SCORE, scores[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,52 +198,58 @@ public class HabitScoreView extends ScrollableDataView
|
||||
|
||||
float lineHeight = pText.getFontSpacing();
|
||||
|
||||
RectF rGrid = new RectF(0, 0, nColumns * columnWidth, columnHeight);
|
||||
rGrid.offset(0, headerHeight);
|
||||
drawGrid(canvas, rGrid);
|
||||
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
||||
rect.offset(0, paddingTop);
|
||||
|
||||
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
|
||||
SimpleDateFormat dfDay = new SimpleDateFormat("d");
|
||||
drawGrid(canvas, rect);
|
||||
|
||||
String previousMonth = "";
|
||||
|
||||
pGraph.setColor(habit.color);
|
||||
RectF prevR = null;
|
||||
pText.setTextAlign(Paint.Align.CENTER);
|
||||
pText.setColor(textColor);
|
||||
pGraph.setColor(primaryColor);
|
||||
prevRect.setEmpty();
|
||||
|
||||
for (int offset = nColumns - scores.size(); offset < nColumns; offset++)
|
||||
long currentDate = DateHelper.getStartOfToday();
|
||||
|
||||
for(int k = 0; k < nColumns + getDataOffset() - 1; k++)
|
||||
currentDate -= 7 * DateHelper.millisecondsInOneDay;
|
||||
|
||||
for (int k = 0; k < nColumns; k++)
|
||||
{
|
||||
Score score = scores.get(offset - nColumns + scores.size());
|
||||
String month = dfMonth.format(score.timestamp);
|
||||
String day = dfDay.format(score.timestamp);
|
||||
String month = dfMonth.format(currentDate);
|
||||
String day = dfDay.format(currentDate);
|
||||
|
||||
long s = score.score;
|
||||
double sRelative = ((double) s) / Habit.MAX_SCORE;
|
||||
int score = 0;
|
||||
int offset = nColumns - k - 1 + getDataOffset();
|
||||
if(offset < scores.length) score = scores[offset];
|
||||
|
||||
double sRelative = ((double) score) / Score.MAX_SCORE;
|
||||
int height = (int) (columnHeight * sRelative);
|
||||
|
||||
RectF r = new RectF(0, 0, columnWidth, columnWidth);
|
||||
r.offset(offset * columnWidth,
|
||||
headerHeight + columnHeight - height - columnWidth / 2);
|
||||
rect.set(0, 0, baseSize, baseSize);
|
||||
rect.offset(k * columnWidth, paddingTop + columnHeight - height - columnWidth / 2);
|
||||
|
||||
if (prevR != null)
|
||||
if (!prevRect.isEmpty())
|
||||
{
|
||||
drawLine(canvas, prevR, r);
|
||||
drawMarker(canvas, prevR);
|
||||
drawLine(canvas, prevRect, rect);
|
||||
drawMarker(canvas, prevRect);
|
||||
}
|
||||
|
||||
if (offset == nColumns - 1) drawMarker(canvas, r);
|
||||
if (k == nColumns - 1) drawMarker(canvas, rect);
|
||||
|
||||
prevR = r;
|
||||
prevRect.set(rect);
|
||||
|
||||
rect.set(0, 0, columnWidth, columnHeight);
|
||||
rect.offset(k * columnWidth, paddingTop);
|
||||
|
||||
r = new RectF(0, 0, columnWidth, columnHeight);
|
||||
r.offset(offset * columnWidth, headerHeight);
|
||||
if (!month.equals(previousMonth))
|
||||
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
|
||||
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
else
|
||||
canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
|
||||
canvas.drawText(day, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
|
||||
previousMonth = month;
|
||||
|
||||
currentDate += 7 * DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +258,10 @@ public class HabitScoreView extends ScrollableDataView
|
||||
int nRows = 5;
|
||||
float rowHeight = rGrid.height() / nRows;
|
||||
|
||||
pGrid.setColor(Color.rgb(240, 240, 240));
|
||||
pText.setTextAlign(Paint.Align.LEFT);
|
||||
pText.setColor(textColor);
|
||||
pGrid.setColor(dimmedTextColor);
|
||||
|
||||
for (int i = 0; i < nRows; i++)
|
||||
{
|
||||
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em,
|
||||
@@ -170,7 +275,7 @@ public class HabitScoreView extends ScrollableDataView
|
||||
|
||||
private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo)
|
||||
{
|
||||
pGraph.setColor(habit.color);
|
||||
pGraph.setColor(primaryColor);
|
||||
canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(),
|
||||
pGraph);
|
||||
}
|
||||
@@ -178,15 +283,32 @@ public class HabitScoreView extends ScrollableDataView
|
||||
private void drawMarker(Canvas canvas, RectF rect)
|
||||
{
|
||||
rect.inset(columnWidth * 0.15f, columnWidth * 0.15f);
|
||||
pGraph.setColor(Color.WHITE);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
|
||||
pGraph.setColor(habit.color);
|
||||
setModeOrColor(pGraph, XFERMODE_SRC, primaryColor);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
|
||||
pGraph.setColor(Color.WHITE);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
pGraph.setXfermode(XFERMODE_SRC);
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
createColors();
|
||||
}
|
||||
|
||||
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
||||
{
|
||||
if(isBackgroundTransparent)
|
||||
p.setXfermode(mode);
|
||||
else
|
||||
p.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
@@ -21,71 +24,194 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Streak;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitStreakView extends ScrollableDataView
|
||||
{
|
||||
private Habit habit;
|
||||
private Paint pText, pBar;
|
||||
private List<Streak> streaks;
|
||||
|
||||
private long[] startTimes;
|
||||
private long[] endTimes;
|
||||
private long[] lengths;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int headerHeight;
|
||||
private int nColumns;
|
||||
|
||||
private long maxStreakLength;
|
||||
private int[] colors;
|
||||
private SimpleDateFormat dfMonth;
|
||||
private Rect rect;
|
||||
private int baseSize;
|
||||
private int primaryColor;
|
||||
|
||||
public HabitStreakView(Context context, Habit habit, int columnWidth)
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private Paint pBarText;
|
||||
|
||||
public HabitStreakView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context);
|
||||
this.habit = habit;
|
||||
|
||||
setDimensions(columnWidth);
|
||||
createPaints();
|
||||
createColors();
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
startTimes = endTimes = lengths = new long[0];
|
||||
init();
|
||||
}
|
||||
|
||||
private void setDimensions(int baseSize)
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.columnWidth = baseSize;
|
||||
this.habit = habit;
|
||||
|
||||
createColors();
|
||||
refreshData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
refreshData();
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
rect = new Rect();
|
||||
}
|
||||
|
||||
@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)
|
||||
{
|
||||
baseSize = height / 10;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
headerHeight = baseSize;
|
||||
footerHeight = baseSize;
|
||||
nColumns = width / baseSize - 1;
|
||||
|
||||
pText.setTextSize(baseSize * 0.5f);
|
||||
pBar.setTextSize(baseSize * 0.5f);
|
||||
}
|
||||
|
||||
private void createColors()
|
||||
{
|
||||
colors = new int[4];
|
||||
colors[0] = Color.rgb(230, 230, 230);
|
||||
colors[3] = habit.color;
|
||||
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
|
||||
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
|
||||
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
|
||||
}
|
||||
|
||||
private void createPaints()
|
||||
int red = Color.red(primaryColor);
|
||||
int green = Color.green(primaryColor);
|
||||
int blue = Color.blue(primaryColor);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
colors = new int[4];
|
||||
colors[3] = primaryColor;
|
||||
colors[2] = Color.argb(213, red, green, blue);
|
||||
colors[1] = Color.argb(170, red, green, blue);
|
||||
colors[0] = Color.argb(128, red, green, blue);
|
||||
textColor = Color.rgb(255, 255, 255);
|
||||
pBarText = pText;
|
||||
}
|
||||
else
|
||||
{
|
||||
colors = new int[4];
|
||||
colors[3] = primaryColor;
|
||||
colors[2] = Color.argb(192, red, green, blue);
|
||||
colors[1] = Color.argb(96, red, green, blue);
|
||||
colors[0] = Color.argb(32, 0, 0, 0);
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
pBarText = pBar;
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
{
|
||||
pText = new Paint();
|
||||
pText.setColor(Color.LTGRAY);
|
||||
pText.setTextAlign(Paint.Align.CENTER);
|
||||
pText.setTextSize(columnWidth * 0.5f);
|
||||
pText.setAntiAlias(true);
|
||||
|
||||
pBar = new Paint();
|
||||
pBar.setTextAlign(Paint.Align.CENTER);
|
||||
pBar.setTextSize(columnWidth * 0.5f);
|
||||
pBar.setAntiAlias(true);
|
||||
}
|
||||
|
||||
protected void fetchData()
|
||||
public void refreshData()
|
||||
{
|
||||
streaks = habit.getStreaks();
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if(habit == null) return;
|
||||
|
||||
List<Streak> streaks = habit.streaks.getAll();
|
||||
int size = streaks.size();
|
||||
|
||||
startTimes = new long[size];
|
||||
endTimes = new long[size];
|
||||
lengths = new long[size];
|
||||
|
||||
int k = 0;
|
||||
for (Streak s : streaks)
|
||||
{
|
||||
startTimes[k] = s.start;
|
||||
endTimes[k] = s.end;
|
||||
lengths[k] = s.length;
|
||||
k++;
|
||||
|
||||
maxStreakLength = Math.max(maxStreakLength, s.length);
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
int size = 30;
|
||||
|
||||
startTimes = new long[size];
|
||||
endTimes = new long[size];
|
||||
lengths = new long[size];
|
||||
|
||||
Random random = new Random();
|
||||
Long date = DateHelper.getStartOfToday();
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
int l = (int) Math.pow(2, random.nextFloat() * 5 + 1);
|
||||
|
||||
endTimes[i] = date;
|
||||
date -= l * DateHelper.millisecondsInOneDay;
|
||||
lengths[i] = (long) l;
|
||||
startTimes[i] = date;
|
||||
|
||||
maxStreakLength = Math.max(maxStreakLength, l);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
@@ -95,32 +221,40 @@ public class HabitStreakView extends ScrollableDataView
|
||||
float lineHeight = pText.getFontSpacing();
|
||||
float barHeaderOffset = lineHeight * 0.4f;
|
||||
|
||||
int nStreaks = streaks.size();
|
||||
int start = Math.max(0, nStreaks - nColumns - dataOffset);
|
||||
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
|
||||
int nStreaks = startTimes.length;
|
||||
int start = nStreaks - nColumns - getDataOffset();
|
||||
|
||||
pText.setColor(textColor);
|
||||
|
||||
String previousMonth = "";
|
||||
|
||||
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
|
||||
{
|
||||
String month = dfMonth.format(streaks.get(start + offset).start);
|
||||
if(start + offset < 0) continue;
|
||||
String month = dfMonth.format(startTimes[start + offset]);
|
||||
|
||||
long l = streaks.get(offset + start).length;
|
||||
long l = lengths[offset + start];
|
||||
double lRelative = ((double) l) / maxStreakLength;
|
||||
|
||||
pBar.setColor(colors[(int) Math.floor(lRelative * 3)]);
|
||||
|
||||
int height = (int) (columnHeight * lRelative);
|
||||
Rect r = new Rect(0, 0, columnWidth - 2, height);
|
||||
r.offset(offset * columnWidth, headerHeight + columnHeight - height);
|
||||
rect.set(0, 0, columnWidth - 2, height);
|
||||
rect.offset(offset * columnWidth, headerHeight + columnHeight - height);
|
||||
|
||||
canvas.drawRect(r, pBar);
|
||||
canvas.drawText(Long.toString(l), r.centerX(), r.top - barHeaderOffset, pBar);
|
||||
canvas.drawRect(rect, pBar);
|
||||
canvas.drawText(Long.toString(l), rect.centerX(), rect.top - barHeaderOffset, pBarText);
|
||||
|
||||
if (!month.equals(previousMonth))
|
||||
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
|
||||
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
|
||||
previousMonth = month;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
createColors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
@@ -21,31 +24,57 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
public class RingView extends View
|
||||
{
|
||||
|
||||
private int size;
|
||||
private int color;
|
||||
private float perc;
|
||||
private float percentage;
|
||||
private Paint pRing;
|
||||
private float lineHeight;
|
||||
private String label;
|
||||
private RectF rect;
|
||||
|
||||
public RingView(Context context, int size, int color, float perc, String label)
|
||||
public RingView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
|
||||
this.size = (int) context.getResources().getDimension(R.dimen.small_square_size) * 4;
|
||||
this.label = DialogHelper.getAttribute(context, attrs, "label");
|
||||
this.color = ColorHelper.palette[7];
|
||||
this.percentage = 0.75f;
|
||||
init();
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
super(context);
|
||||
this.size = size;
|
||||
this.color = color;
|
||||
this.perc = perc;
|
||||
|
||||
pRing = new Paint();
|
||||
pRing.setColor(color);
|
||||
pRing.setAntiAlias(true);
|
||||
pRing.setTextAlign(Paint.Align.CENTER);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
this.label = label;
|
||||
public void setPercentage(float percentage)
|
||||
{
|
||||
this.percentage = percentage;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
pRing = new Paint();
|
||||
pRing.setAntiAlias(true);
|
||||
pRing.setColor(color);
|
||||
pRing.setTextAlign(Paint.Align.CENTER);
|
||||
pRing.setTextSize(size * 0.2f);
|
||||
lineHeight = pRing.getFontSpacing();
|
||||
rect = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,21 +91,20 @@ public class RingView extends View
|
||||
float thickness = size * 0.15f;
|
||||
|
||||
pRing.setColor(color);
|
||||
RectF r = new RectF(0, 0, size, size);
|
||||
canvas.drawArc(r, -90, 360 * perc, true, pRing);
|
||||
rect.set(0, 0, size, size);
|
||||
canvas.drawArc(rect, -90, 360 * percentage, true, pRing);
|
||||
|
||||
pRing.setColor(Color.rgb(230, 230, 230));
|
||||
canvas.drawArc(r, 360 * perc - 90 + 2, 360 * (1 - perc) - 4, true, pRing);
|
||||
canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing);
|
||||
|
||||
pRing.setColor(Color.WHITE);
|
||||
r.inset(thickness, thickness);
|
||||
canvas.drawArc(r, -90, 360, true, pRing);
|
||||
rect.inset(thickness, thickness);
|
||||
canvas.drawArc(rect, -90, 360, true, pRing);
|
||||
|
||||
pRing.setColor(Color.GRAY);
|
||||
pRing.setTextSize(size * 0.2f);
|
||||
lineHeight = pRing.getFontSpacing();
|
||||
canvas.drawText(String.format("%.0f%%", perc * 100), r.centerX(),
|
||||
r.centerY() + lineHeight / 3, pRing);
|
||||
canvas.drawText(String.format("%.0f%%", percentage * 100), rect.centerX(),
|
||||
rect.centerY() + lineHeight / 3, pRing);
|
||||
|
||||
pRing.setTextSize(size * 0.15f);
|
||||
canvas.drawText(label, size / 2, size + lineHeight * 1.2f, pRing);
|
||||
|
||||
@@ -1,105 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alinson Santos Xavier
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
|
||||
public abstract class ScrollableDataView extends View
|
||||
public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener,
|
||||
ValueAnimator.AnimatorUpdateListener
|
||||
{
|
||||
|
||||
protected int dataOffset;
|
||||
protected int nColumns;
|
||||
protected int columnWidth, columnHeight;
|
||||
protected int headerHeight, footerHeight;
|
||||
private int dataOffset;
|
||||
private int scrollerBucketSize;
|
||||
|
||||
private float prevX, prevY;
|
||||
private GestureDetector detector;
|
||||
private Scroller scroller;
|
||||
private ValueAnimator scrollAnimator;
|
||||
|
||||
public ScrollableDataView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
protected abstract void fetchData();
|
||||
|
||||
protected boolean move(float dx)
|
||||
public ScrollableDataView(Context context, AttributeSet attrs)
|
||||
{
|
||||
int newDataOffset = dataOffset + (int) (dx / columnWidth);
|
||||
newDataOffset = Math.max(0, newDataOffset);
|
||||
|
||||
if (newDataOffset != dataOffset)
|
||||
{
|
||||
dataOffset = newDataOffset;
|
||||
fetchData();
|
||||
invalidate();
|
||||
return true;
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
else return false;
|
||||
|
||||
private void init(Context context)
|
||||
{
|
||||
detector = new GestureDetector(context, this);
|
||||
scroller = new Scroller(context, null, true);
|
||||
scrollAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
scrollAnimator.addUpdateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getAction();
|
||||
|
||||
int pointerIndex = MotionEventCompat.getActionIndex(event);
|
||||
final float x = MotionEventCompat.getX(event, pointerIndex);
|
||||
final float y = MotionEventCompat.getY(event, pointerIndex);
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN)
|
||||
{
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
return detector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_MOVE)
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e)
|
||||
{
|
||||
float dx = x - prevX;
|
||||
float dy = y - prevY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Math.abs(dy) > Math.abs(dx)) return false;
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
|
||||
{
|
||||
if(scrollerBucketSize == 0)
|
||||
return false;
|
||||
|
||||
if(Math.abs(dx) > Math.abs(dy))
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
if (move(dx))
|
||||
{
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
}
|
||||
|
||||
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
|
||||
scroller.computeScrollOffset();
|
||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
||||
postInvalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
public void onLongPress(MotionEvent e)
|
||||
{
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh)
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
|
||||
{
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
nColumns = w / columnWidth;
|
||||
fetchData();
|
||||
scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000,
|
||||
0, 0);
|
||||
invalidate();
|
||||
|
||||
scrollAnimator.setDuration(scroller.getDuration());
|
||||
scrollAnimator.start();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation)
|
||||
{
|
||||
if (!scroller.isFinished())
|
||||
{
|
||||
scroller.computeScrollOffset();
|
||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
||||
postInvalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public int getDataOffset()
|
||||
{
|
||||
return dataOffset;
|
||||
}
|
||||
|
||||
public void setScrollerBucketSize(int scrollerBucketSize)
|
||||
{
|
||||
this.scrollerBucketSize = scrollerBucketSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.Image;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
{
|
||||
|
||||
private int width, height;
|
||||
|
||||
protected abstract int getDefaultHeight();
|
||||
|
||||
protected abstract int getDefaultWidth();
|
||||
|
||||
protected abstract PendingIntent getOnClickPendingIntent(Context context, Habit habit);
|
||||
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
protected abstract View buildCustomView(Context context, Habit habit);
|
||||
|
||||
public static String getHabitIdKey(long widgetId)
|
||||
{
|
||||
return String.format("widget-%06d-habit", widgetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleted(Context context, int[] appWidgetIds)
|
||||
{
|
||||
Context appContext = context.getApplicationContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
|
||||
for(Integer id : appWidgetIds)
|
||||
prefs.edit().remove(getHabitIdKey(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
|
||||
int appWidgetId, Bundle newOptions)
|
||||
{
|
||||
updateWidget(context, appWidgetManager, appWidgetId, newOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds)
|
||||
{
|
||||
for(int id : appWidgetIds)
|
||||
{
|
||||
Bundle options = null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
|
||||
options = manager.getAppWidgetOptions(id);
|
||||
|
||||
updateWidget(context, manager, id, options);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidget(Context context, AppWidgetManager manager, int widgetId, Bundle options)
|
||||
{
|
||||
updateWidgetSize(context, options);
|
||||
|
||||
Context appContext = context.getApplicationContext();
|
||||
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
|
||||
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L);
|
||||
if(habitId < 0) return;
|
||||
|
||||
Habit habit = Habit.get(habitId);
|
||||
View widgetView = buildCustomView(context, habit);
|
||||
measureCustomView(context, width, height, widgetView);
|
||||
|
||||
widgetView.setDrawingCacheEnabled(true);
|
||||
widgetView.buildDrawingCache(true);
|
||||
Bitmap drawingCache = widgetView.getDrawingCache();
|
||||
|
||||
remoteViews.setTextViewText(R.id.label, habit.name);
|
||||
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
|
||||
|
||||
//savePreview(context, widgetId, drawingCache);
|
||||
|
||||
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
|
||||
if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);
|
||||
|
||||
manager.updateAppWidget(widgetId, remoteViews);
|
||||
}
|
||||
|
||||
private void savePreview(Context context, int widgetId, Bitmap widgetCache)
|
||||
{
|
||||
try
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View view = inflater.inflate(getLayoutId(), null);
|
||||
|
||||
ImageView iv = (ImageView) view.findViewById(R.id.imageView);
|
||||
iv.setImageBitmap(widgetCache);
|
||||
|
||||
view.measure(width, height);
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
view.setDrawingCacheEnabled(true);
|
||||
view.buildDrawingCache();
|
||||
Bitmap previewCache = view.getDrawingCache();
|
||||
|
||||
String filename = String.format("%s/%d.png", context.getExternalCacheDir(), widgetId);
|
||||
Log.d("BaseWidgetProvider", String.format("Writing %s", filename));
|
||||
FileOutputStream out = new FileOutputStream(filename);
|
||||
|
||||
if(previewCache != null)
|
||||
previewCache.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
|
||||
out.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidgetSize(Context context, Bundle options)
|
||||
{
|
||||
int maxWidth = getDefaultWidth();
|
||||
int minWidth = getDefaultWidth();
|
||||
int maxHeight = getDefaultHeight();
|
||||
int minHeight = getDefaultHeight();
|
||||
|
||||
if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
{
|
||||
maxWidth = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
|
||||
maxHeight = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
|
||||
minWidth = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
|
||||
minHeight = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
|
||||
}
|
||||
|
||||
width = maxWidth;
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
private void measureCustomView(Context context, int w, int h, View customView)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View entireView = inflater.inflate(getLayoutId(), null);
|
||||
|
||||
int specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
|
||||
int specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
|
||||
|
||||
entireView.measure(specWidth, specHeight);
|
||||
entireView.layout(0, 0, entireView.getMeasuredWidth(), entireView.getMeasuredHeight());
|
||||
|
||||
View imageView = entireView.findViewById(R.id.imageView);
|
||||
w = imageView.getMeasuredWidth();
|
||||
h = imageView.getMeasuredHeight();
|
||||
|
||||
specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
|
||||
specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
|
||||
customView.measure(specWidth, specHeight);
|
||||
customView.layout(0, 0, customView.getMeasuredWidth(), customView.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.CheckmarkView;
|
||||
|
||||
public class CheckmarkWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
CheckmarkView view = new CheckmarkView(context);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
|
||||
{
|
||||
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_checkmark;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.isoron.uhabits.MainActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.widgets.BaseWidgetProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitPickerDialog extends Activity implements AdapterView.OnItemClickListener
|
||||
{
|
||||
|
||||
private Integer widgetId;
|
||||
private ArrayList<Long> habitIds;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.widget_configure_activity);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras != null) widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID);
|
||||
|
||||
ListView listView = (ListView) findViewById(R.id.listView);
|
||||
|
||||
habitIds = new ArrayList<>();
|
||||
ArrayList<String> habitNames = new ArrayList<>();
|
||||
|
||||
List<Habit> habits = Habit.getAll(false);
|
||||
for(Habit h : habits)
|
||||
{
|
||||
habitIds.add(h.getId());
|
||||
habitNames.add(h.name);
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, habitNames);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Long habitId = habitIds.get(position);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
prefs.edit().putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId).commit();
|
||||
|
||||
MainActivity.updateWidgets(this);
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
|
||||
setResult(RESULT_OK, resultValue);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.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.HabitHistoryView;
|
||||
|
||||
public class HistoryWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitHistoryView view = new HabitHistoryView(context, null);
|
||||
view.setHabit(habit);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.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.HabitScoreView;
|
||||
|
||||
public class ScoreWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitScoreView view = new HabitScoreView(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.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.HabitStreakView;
|
||||
|
||||
public class StreakWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitStreakView view = new HabitStreakView(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;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 394 B |
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
|
||||
27
app/src/main/res/drawable-v21/ripple_transparent.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:id="@android:id/mask">
|
||||
<color android:color="@android:color/white" />
|
||||
</item>
|
||||
|
||||
</ripple>
|
||||
24
app/src/main/res/drawable-v21/ripple_white.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:drawable="@color/white" />
|
||||
</ripple>
|
||||
|
Before Width: | Height: | Size: 289 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 716 B |
|
Before Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:top="0dp"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item android:top="40dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="#30000000"
|
||||
android:endColor="#00000000"
|
||||
android:angle="270"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:top="21dp" android:bottom="2dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="#ccffffff"
|
||||
android:startColor="#ffffff" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:bottom="21dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<solid android:color="#ffffff" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
|
||||
</layer-list>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item android:bottom="28dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="#00000000"
|
||||
android:height="1px"
|
||||
android:startColor="#08000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -1,7 +0,0 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@drawable/habits_item_check_pressed" android:state_pressed="true"></item>
|
||||
<item android:drawable="@drawable/habits_item_check_pressed" android:state_focused="true"></item>
|
||||
<item android:drawable="@drawable/habits_item_check_normal"></item>
|
||||
|
||||
</selector>
|
||||
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item android:top="40dp">
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:id="@android:id/mask">
|
||||
<color android:color="@android:color/white" />
|
||||
</item>
|
||||
|
||||
</ripple>
|
||||
@@ -1,5 +0,0 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:drawable="@color/white" />
|
||||
</ripple>
|
||||
@@ -1,4 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="@color/grey_100" />
|
||||
<stroke android:width="2dip" android:color="@color/grey_500"/>
|
||||
|
||||
26
app/src/main/res/drawable/widget_background.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="#3f000000" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
BIN
app/src/main/res/drawable/widget_preview_checkmark.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/drawable/widget_preview_frequency.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
app/src/main/res/drawable/widget_preview_history.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
app/src/main/res/drawable/widget_preview_score.png
Normal file
|
After Width: | Height: | Size: 19 KiB |