Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2dd26eeb3 | |||
| 5831340343 | |||
| d7f6f52a49 | |||
| 93b442332d | |||
| e248824bcd | |||
| 00774368d4 | |||
| cbf1bd3e19 | |||
| 4061921b93 | |||
| 59d42fe62f | |||
| 88e8aad0d8 | |||
| a4b6728721 | |||
| 33bc8f78e7 | |||
| 0f223f8504 | |||
| 007996c69e | |||
| ff9d50b32a | |||
| 38024d71ce | |||
| 9ec1afc208 | |||
| e0888a9b4d | |||
| 8aade2f145 | |||
| 1ba1d775f7 | |||
| bd6cba8303 | |||
| befc3a0ad8 | |||
| f4c963e2c1 | |||
| e9a4a047c1 | |||
| 7ce1988d2e | |||
| 53911fa410 | |||
| f5f43d9a16 | |||
| d0e475ad78 | |||
| b3199cf092 | |||
| 73e6f2a2d4 | |||
| 097a5be864 | |||
| 0287473e61 | |||
| f403dc6f77 | |||
| 23466523df | |||
| 83ca136346 | |||
| 7830b599db | |||
| 49376d4f41 | |||
| 8ee9c9c0b1 | |||
| ee9da29422 | |||
| ae7b0408b2 | |||
| d926d9dbd6 | |||
| 5694da2413 | |||
| 7e0ae144b8 | |||
| 1426d887e7 | |||
| acb8e820fd | |||
| eae0d66f51 | |||
| e933cbbc43 | |||
| 0b95b6a78c | |||
| 5e873a3659 | |||
| d292ecd988 | |||
| 19b484c368 | |||
| 834ae92d87 | |||
| 26ce92d381 | |||
| 0eb12812d4 | |||
| a1a636c718 | |||
| b2811b9797 | |||
| 4db7a6e89c | |||
| 677a643e5b | |||
| dbca3238f6 | |||
| ae5dd700f3 | |||
| 7e4c508d7d | |||
| d0c056eeaa | |||
| f172c69eed | |||
| 0cd4a41438 | |||
| d77c78249c | |||
| d9d48ff984 | |||
| 7be71ba4f8 | |||
| f0534fefbc | |||
| 30ef75bb45 | |||
| 44ed74a693 | |||
| 4aea527e07 | |||
| 638539259a | |||
| 75dec88411 | |||
| 2dc4fcbc46 | |||
| 7977d5247c | |||
| 8b18e32a16 | |||
| dbcad9a5f4 | |||
| 3bba75ff50 | |||
| 9bbeee66e2 | |||
| c6e76f4cfd | |||
| 980db1d171 | |||
| aee5d975db | |||
| e2bb4371d3 | |||
| fcee8552f0 | |||
| a4864e4612 | |||
| 8ddf4574e3 | |||
| f1ca8c5449 | |||
| 81a188f125 | |||
| 44402bf3a4 | |||
| 7f159149ef | |||
| 0c1e8d5131 | |||
| a4e3f7e037 | |||
| b354a0765b | |||
| bbd959dfda | |||
| d05d404c55 | |||
| 8938b0c9a6 | |||
| bd6fcd066c | |||
| 6a5f2abb76 | |||
| 33b5215b00 | |||
| 5d808dd2e8 | |||
| 767ec1b6de | |||
| 9e5f3d8f58 | |||
| 775d028629 | |||
| 4bfb839370 | |||
| 8f64b696d8 | |||
| c4bf31e778 | |||
| c757ce6548 | |||
| 78994bb3c4 | |||
| 2373ba5407 | |||
| b601a643dd | |||
| 90e513e778 | |||
| 728c9557f0 | |||
| 5bd0c842f5 | |||
| 97711087f9 | |||
| 2fe2d8834a | |||
| 93a893d660 | |||
| feff9a18e6 | |||
| b42565b770 | |||
| 3de702ced2 | |||
| 42f7f4042d | |||
| 2115a590f2 | |||
| f5f3ec9f88 | |||
| 6d5a8f5753 | |||
| 06fc04092b | |||
|
|
24c02605d1 | ||
| 11fcb67624 | |||
| abc92e390a | |||
| 52c07660b1 | |||
| 35f778c376 | |||
| 436ae7e574 | |||
| 5c683a77c2 | |||
| dd1f6c9efc | |||
| cba089407b | |||
| a7b0395a2a | |||
| 7d2946360f | |||
| 04e8432522 | |||
| 13d34945b4 | |||
| 756578508e | |||
| 38fc7650b9 | |||
| cf06ec0a4b | |||
| f0701f7b35 | |||
| de8018af7d | |||
| ac885e1503 | |||
| f85e09288c | |||
| 6f11596bb7 |
46
CHANGELOG.md
@@ -1,6 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
### 1.4.0 (April 4, 2016)
|
||||
### 1.5.4 (May 29, 2016)
|
||||
|
||||
* Fix crash upon opening settings screen in some phones
|
||||
* Fix missing folders in CSV archive
|
||||
* Add Serbian translation
|
||||
|
||||
### 1.5.3 (May 22, 2016)
|
||||
|
||||
* Complete Arabic and Czech translations
|
||||
* Fix crash at startup
|
||||
* Fix checkmark widget on custom launchers
|
||||
|
||||
### 1.5.2 (May 19, 2016)
|
||||
|
||||
* Fix missing attachment on bug reports
|
||||
* Fix bug that prevents some widgets from rendering
|
||||
* Complete Japanese translation
|
||||
|
||||
### 1.5.1 (May 17, 2016)
|
||||
|
||||
* Fix build on F-Droid
|
||||
|
||||
### 1.5.0 (May 15, 2016)
|
||||
|
||||
* Add night mode, with AMOLED support
|
||||
* Backport material design to older devices
|
||||
* Display more information on statistics screen
|
||||
* Display score on main screen and checkmark widget
|
||||
* Make widgets react immediately to touch
|
||||
* Reschedule reminders after reboot
|
||||
* Pick first day of the week according to country
|
||||
* Add option to reverse order of days on main screen
|
||||
* Add option to change notification sounds
|
||||
* Add Catalan, Indonesian, Turkish, Ukrainian translations
|
||||
* Switch between Simplified/Traditional Chinese according to country
|
||||
|
||||
### 1.4.1 (April 9, 2016)
|
||||
|
||||
* Show error message on widgets, instead of crashing
|
||||
* Complete French translation
|
||||
* Minor fixes to other translations
|
||||
|
||||
### 1.4.0 (April 7, 2016)
|
||||
|
||||
* Ability to import data from third-party apps
|
||||
* Ability to save and restore full database backup
|
||||
@@ -62,4 +104,4 @@
|
||||
|
||||
### 1.0.0 (February 19, 2016)
|
||||
|
||||
* Initial release
|
||||
* Initial release
|
||||
|
||||
@@ -54,6 +54,7 @@ source.
|
||||
[![Habit strength][screen3th]][screen3]
|
||||
[![Habit history and streaks][screen4th]][screen4]
|
||||
[![Widgets][screen5th]][screen5]
|
||||
[![Night mode][screen6th]][screen6]
|
||||
|
||||
## Installing
|
||||
|
||||
@@ -71,7 +72,8 @@ 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.
|
||||
at GitHub. If you would like to receive the newest versions of the app
|
||||
earlier than everyone else, [join our open beta on Google Play][beta].
|
||||
|
||||
* **Spread the word.** If you like the app, share it with your family, friends
|
||||
and colleagues. You can also rate and review the app on Google Play Store, to help
|
||||
@@ -105,14 +107,17 @@ contribute, even if you are not a software developer.
|
||||
[screen3]: screenshots/original/uhabits3.png
|
||||
[screen4]: screenshots/original/uhabits4.png
|
||||
[screen5]: screenshots/original/uhabits5.png
|
||||
[screen6]: screenshots/original/uhabits6.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
|
||||
[screen6th]: screenshots/thumbs/uhabits6.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
|
||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||
|
||||
@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.isoron.uhabits"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 23
|
||||
|
||||
buildConfigField "Integer", "databaseVersion", "13"
|
||||
buildConfigField "Integer", "databaseVersion", "14"
|
||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
@@ -32,18 +32,32 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
compile 'com.android.support:support-v4:23.3.0'
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile 'com.android.support:design:23.3.0'
|
||||
compile 'com.android.support:preference-v14:23.3.0'
|
||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||
compile 'org.apmem.tools:layouts:1.10@aar'
|
||||
compile 'com.opencsv:opencsv:3.7'
|
||||
compile project(':libs:drag-sort-listview:library')
|
||||
compile files('libs/ActiveAndroid.jar')
|
||||
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
|
||||
|
||||
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'
|
||||
compile project(':libs:drag-sort-listview:library')
|
||||
|
||||
androidTestCompile 'com.android.support:support-annotations:23.3.0'
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB |
@@ -25,6 +25,7 @@ import android.os.Looper;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.junit.Before;
|
||||
|
||||
@@ -50,6 +51,7 @@ public class BaseTest
|
||||
targetContext = InstrumentationRegistry.getTargetContext();
|
||||
testContext = InstrumentationRegistry.getContext();
|
||||
|
||||
UIHelper.setFixedTheme(R.style.AppBaseTheme);
|
||||
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.support.test.espresso.NoMatchingViewException;
|
||||
import android.support.test.espresso.contrib.RecyclerViewActions;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -31,7 +32,6 @@ 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;
|
||||
@@ -39,12 +39,16 @@ 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.RootMatchers.isPlatformPopup;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
@@ -168,13 +172,13 @@ public class MainActivityActions
|
||||
public static void deleteHabits(List<String> names)
|
||||
{
|
||||
selectHabits(names);
|
||||
clickActionModeMenuItem(R.string.delete);
|
||||
clickMenuItem(R.string.delete);
|
||||
onView(withText("OK"))
|
||||
.perform(click());
|
||||
assertHabitsDontExist(names);
|
||||
}
|
||||
|
||||
public static void clickActionModeMenuItem(int stringId)
|
||||
public static void clickMenuItem(int stringId)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -188,9 +192,34 @@ public class MainActivityActions
|
||||
}
|
||||
catch(Exception e2)
|
||||
{
|
||||
openContextualActionModeOverflowMenu();
|
||||
onView(withText(stringId)).perform(click());
|
||||
clickHiddenMenuItem(stringId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void clickHiddenMenuItem(int stringId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try the ActionMode overflow menu first
|
||||
onView(allOf(withContentDescription("More options"), withParent(withParent(
|
||||
withClassName(containsString("Action")))))).perform(click());
|
||||
}
|
||||
catch (Exception e1)
|
||||
{
|
||||
// Try the toolbar overflow menu
|
||||
onView(allOf(withContentDescription("More options"), withParent(withParent(
|
||||
withClassName(containsString("Toolbar")))))).perform(click());
|
||||
}
|
||||
|
||||
onView(withText(stringId)).perform(click());
|
||||
}
|
||||
|
||||
public static void clickSettingsItem(String text)
|
||||
{
|
||||
onView(withClassName(containsString("RecyclerView")))
|
||||
.perform(RecyclerViewActions.actionOnItem(
|
||||
hasDescendant(withText(containsString(text))),
|
||||
click()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ 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;
|
||||
@@ -61,7 +60,6 @@ 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.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
@@ -69,7 +67,6 @@ import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.isoron.uhabits.ui.HabitMatchers.isPreferenceWithText;
|
||||
import static org.isoron.uhabits.ui.HabitMatchers.withName;
|
||||
import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations;
|
||||
import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks;
|
||||
@@ -77,7 +74,8 @@ import static org.isoron.uhabits.ui.MainActivityActions.addHabit;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.clickActionModeMenuItem;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.clickMenuItem;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.clickSettingsItem;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
|
||||
@@ -152,20 +150,16 @@ public class MainTest
|
||||
|
||||
selectHabits(names);
|
||||
|
||||
clickActionModeMenuItem(R.string.archive);
|
||||
clickMenuItem(R.string.archive);
|
||||
assertHabitsDontExist(names);
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.show_archived))
|
||||
.perform(click());
|
||||
clickMenuItem(R.string.show_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
selectHabits(names);
|
||||
clickActionModeMenuItem(R.string.unarchive);
|
||||
clickMenuItem(R.string.unarchive);
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.show_archived))
|
||||
.perform(click());
|
||||
clickMenuItem(R.string.show_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
deleteHabits(names);
|
||||
@@ -227,7 +221,7 @@ public class MainTest
|
||||
.onChildView(withId(R.id.label))
|
||||
.perform(longClick());
|
||||
|
||||
clickActionModeMenuItem(R.string.edit);
|
||||
clickMenuItem(R.string.edit);
|
||||
|
||||
String modifiedName = "Modified " + new Random().nextInt(10000);
|
||||
typeHabitData(modifiedName, "", "1", "1");
|
||||
@@ -238,7 +232,7 @@ public class MainTest
|
||||
assertHabitExists(modifiedName);
|
||||
|
||||
selectHabit(modifiedName);
|
||||
clickActionModeMenuItem(R.string.color_picker_default_title);
|
||||
clickMenuItem(R.string.color_picker_default_title);
|
||||
pressBack();
|
||||
|
||||
deleteHabit(modifiedName);
|
||||
@@ -272,8 +266,7 @@ public class MainTest
|
||||
@Test
|
||||
public void testSettings()
|
||||
{
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.settings)).perform(click());
|
||||
clickMenuItem(R.string.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,8 +275,7 @@ public class MainTest
|
||||
@Test
|
||||
public void testAbout()
|
||||
{
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.about)).perform(click());
|
||||
clickMenuItem(R.string.about);
|
||||
onView(isRoot()).perform(swipeUp());
|
||||
}
|
||||
|
||||
@@ -293,8 +285,7 @@ public class MainTest
|
||||
@Test
|
||||
public void testHelp()
|
||||
{
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.help)).perform(click());
|
||||
clickMenuItem(R.string.help);
|
||||
intended(hasAction(Intent.ACTION_VIEW));
|
||||
}
|
||||
|
||||
@@ -307,20 +298,18 @@ public class MainTest
|
||||
{
|
||||
String name = addHabit();
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.settings)).perform(click());
|
||||
clickMenuItem(R.string.settings);
|
||||
|
||||
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
|
||||
date = date.substring(0, date.length() - 2);
|
||||
|
||||
onData(isPreferenceWithText("Export full backup")).perform(click());
|
||||
clickSettingsItem("Export full backup");
|
||||
intended(hasAction(Intent.ACTION_SEND));
|
||||
|
||||
deleteHabit(name);
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.settings)).perform(click());
|
||||
onData(isPreferenceWithText("Import data")).perform(click());
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Import data");
|
||||
|
||||
onData(allOf(is(instanceOf(String.class)), startsWith("Backups")))
|
||||
.perform(click());
|
||||
@@ -339,9 +328,8 @@ public class MainTest
|
||||
public void testExportCSV()
|
||||
{
|
||||
addHabit();
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.settings)).perform(click());
|
||||
onData(isPreferenceWithText("Export as CSV")).perform(click());
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Export as CSV");
|
||||
intended(hasAction(Intent.ACTION_SEND));
|
||||
}
|
||||
|
||||
@@ -351,9 +339,8 @@ public class MainTest
|
||||
@Test
|
||||
public void testGenerateBugReport()
|
||||
{
|
||||
openActionBarOverflowOrOptionsMenu(targetContext);
|
||||
onView(withText(R.string.settings)).perform(click());
|
||||
onData(isPreferenceWithText("Generate bug report")).perform(click());
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Generate bug report");
|
||||
intended(hasAction(Intent.ACTION_SENDTO));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -66,7 +65,7 @@ public class HabitFixtures
|
||||
Habit habit = new Habit();
|
||||
habit.name = "Meditate";
|
||||
habit.description = "Did you meditate this morning?";
|
||||
habit.color = ColorHelper.palette[3];
|
||||
habit.color = 3;
|
||||
habit.freqNum = 1;
|
||||
habit.freqDen = 1;
|
||||
habit.save();
|
||||
@@ -78,7 +77,7 @@ public class HabitFixtures
|
||||
Habit habit = createEmptyHabit();
|
||||
habit.freqNum = 3;
|
||||
habit.freqDen = 7;
|
||||
habit.color = ColorHelper.palette[4];
|
||||
habit.color = 4;
|
||||
habit.save();
|
||||
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
@@ -53,12 +52,12 @@ public class ChangeHabitColorCommandTest extends BaseTest
|
||||
for(int i = 0; i < 3; i ++)
|
||||
{
|
||||
Habit habit = HabitFixtures.createShortHabit();
|
||||
habit.color = ColorHelper.palette[i+1];
|
||||
habit.color = i+1;
|
||||
habit.save();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
command = new ChangeHabitColorCommand(habits, ColorHelper.palette[0]);
|
||||
command = new ChangeHabitColorCommand(habits, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,12 +79,12 @@ public class ChangeHabitColorCommandTest extends BaseTest
|
||||
{
|
||||
int k = 0;
|
||||
for(Habit h : habits)
|
||||
assertThat(h.color, equalTo(ColorHelper.palette[++k]));
|
||||
assertThat(h.color, equalTo(++k));
|
||||
}
|
||||
|
||||
private void checkNewColors()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
assertThat(h.color, equalTo(ColorHelper.palette[0]));
|
||||
assertThat(h.color, equalTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
@@ -78,7 +77,7 @@ public class HabitTest extends BaseTest
|
||||
Habit model = new Habit();
|
||||
model.archived = 1;
|
||||
model.highlight = 1;
|
||||
model.color = Color.BLACK;
|
||||
model.color = 0;
|
||||
model.freqNum = 10;
|
||||
model.freqDen = 20;
|
||||
model.reminderDays = 1;
|
||||
|
||||
@@ -22,10 +22,12 @@ package org.isoron.uhabits.unit.views;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.CheckmarkView;
|
||||
import org.isoron.uhabits.views.CheckmarkWidgetView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -34,18 +36,19 @@ import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CheckmarkViewTest extends ViewTest
|
||||
public class CheckmarkWidgetViewTest extends ViewTest
|
||||
{
|
||||
private CheckmarkView view;
|
||||
private CheckmarkWidgetView view;
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
UIHelper.setFixedTheme(R.style.TransparentWidgetTheme);
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
view = new CheckmarkView(targetContext);
|
||||
view = new CheckmarkWidgetView(targetContext);
|
||||
view.setHabit(habit);
|
||||
refreshData(view);
|
||||
measureView(dpToPixels(100), dpToPixels(200), view);
|
||||
@@ -52,7 +52,7 @@ public class HabitHistoryViewTest extends ViewTest
|
||||
|
||||
view = new HabitHistoryView(targetContext);
|
||||
view.setHabit(habit);
|
||||
measureView(dpToPixels(300), dpToPixels(100), view);
|
||||
measureView(dpToPixels(400), dpToPixels(200), view);
|
||||
refreshData(view);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class HabitHistoryViewTest extends ViewTest
|
||||
public void tapDate_withEditableView() throws Throwable
|
||||
{
|
||||
view.setIsEditable(true);
|
||||
tap(view, 270, 30);
|
||||
tap(view, 340, 40); // today's square
|
||||
waitForAsyncTasks();
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
@@ -102,9 +102,9 @@ public class HabitHistoryViewTest extends ViewTest
|
||||
int expectedCheckmarkValues[] = habit.checkmarks.getAllValues();
|
||||
|
||||
view.setIsEditable(true);
|
||||
tap(view, 45, 5); // header
|
||||
tap(view, 270, 43); // tomorrow's square
|
||||
tap(view, 280, 30); // right axis
|
||||
tap(view, 118, 13); // header
|
||||
tap(view, 336, 60); // tomorrow's square
|
||||
tap(view, 370, 60); // right axis
|
||||
waitForAsyncTasks();
|
||||
|
||||
int actualCheckmarkValues[] = habit.checkmarks.getAllValues();
|
||||
@@ -115,7 +115,7 @@ public class HabitHistoryViewTest extends ViewTest
|
||||
public void tapDate_withReadOnlyView() throws Throwable
|
||||
{
|
||||
view.setIsEditable(false);
|
||||
tap(view, 270, 30);
|
||||
tap(view, 340, 40); // today's square
|
||||
waitForAsyncTasks();
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
|
||||
@@ -49,7 +49,7 @@ public class HabitScoreViewTest extends ViewTest
|
||||
view.setHabit(habit);
|
||||
view.setBucketSize(7);
|
||||
refreshData(view);
|
||||
measureView(dpToPixels(300), dpToPixels(100), view);
|
||||
measureView(dpToPixels(300), dpToPixels(200), view);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,7 +62,7 @@ public class HabitScoreViewTest extends ViewTest
|
||||
@Test
|
||||
public void testRender_withTransparentBackground() throws Throwable
|
||||
{
|
||||
view.setIsBackgroundTransparent(true);
|
||||
view.setIsTransparencyEnabled(true);
|
||||
assertRenders(view, "HabitScoreView/renderTransparent.png");
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public class NumberViewTest extends ViewTest
|
||||
view = new NumberView(targetContext);
|
||||
view.setLabel("Hello world");
|
||||
view.setNumber(31);
|
||||
view.setColor(ColorHelper.palette[0]);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[0]);
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@ public class NumberViewTest extends ViewTest
|
||||
public void testRender_withDifferentParams() throws IOException
|
||||
{
|
||||
view.setNumber(500);
|
||||
view.setColor(ColorHelper.palette[5]);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[5]);
|
||||
view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize));
|
||||
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "NumberView/renderDifferentParams.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
@@ -42,10 +43,11 @@ public class RingViewTest extends ViewTest
|
||||
super.setup();
|
||||
|
||||
view = new RingView(targetContext);
|
||||
view.setLabel("Hello world");
|
||||
view.setPercentage(0.6f);
|
||||
view.setColor(ColorHelper.palette[0]);
|
||||
view.setMaxDiameter(dpToPixels(100));
|
||||
view.setText("60%");
|
||||
view.setColor(ColorHelper.CSV_PALETTE[0]);
|
||||
view.setBackgroundColor(Color.WHITE);
|
||||
view.setThickness(dpToPixels(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,22 +57,11 @@ public class RingViewTest extends ViewTest
|
||||
assertRenders(view, "RingView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withLongLabel() throws IOException
|
||||
{
|
||||
view.setLabel("The quick brown fox jumps over the lazy fox");
|
||||
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
assertRenders(view, "RingView/renderLongLabel.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentParams() throws IOException
|
||||
{
|
||||
view.setLabel("Habit Strength");
|
||||
view.setPercentage(0.25f);
|
||||
view.setMaxDiameter(dpToPixels(50));
|
||||
view.setColor(ColorHelper.palette[5]);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[5]);
|
||||
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "RingView/renderDifferentParams.png");
|
||||
|
||||
@@ -80,8 +80,8 @@ public class ViewTest extends BaseTest
|
||||
|
||||
if(!similarEnough)
|
||||
{
|
||||
saveBitmap(expectedImagePath, ".scaledExpected", scaledExpected);
|
||||
String path = saveBitmap(expectedImagePath, ".actual", actual);
|
||||
saveBitmap(expectedImagePath, ".expected", scaledExpected);
|
||||
String path = saveBitmap(expectedImagePath, "", actual);
|
||||
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
|
||||
fail(errorMessage.toString());
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="14"
|
||||
android:versionName="1.4.0">
|
||||
android:versionCode="21"
|
||||
android:versionName="1.5.5">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name="HabitsApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -59,8 +61,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".ShowHabitActivity"
|
||||
android:label="@string/title_activity_show_habit"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:label="@string/title_activity_show_habit">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
@@ -68,8 +69,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:label="@string/settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
@@ -90,8 +90,10 @@
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/about"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:label="@string/about">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
@@ -154,7 +156,11 @@
|
||||
android:resource="@xml/widget_frequency_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".HabitBroadcastReceiver"/>
|
||||
<receiver android:name=".HabitBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
14
app/src/main/assets/migrations/14.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
update habits set color=0 where color=-2937041;
|
||||
update habits set color=1 where color=-1684967;
|
||||
update habits set color=2 where color=-415707;
|
||||
update habits set color=3 where color=-5262293;
|
||||
update habits set color=4 where color=-13070788;
|
||||
update habits set color=5 where color=-16742021;
|
||||
update habits set color=6 where color=-16732991;
|
||||
update habits set color=7 where color=-16540699;
|
||||
update habits set color=8 where color=-10603087;
|
||||
update habits set color=9 where color=-7461718;
|
||||
update habits set color=10 where color=-2614432;
|
||||
update habits set color=11 where color=-13619152;
|
||||
update habits set color=12 where color=-5592406;
|
||||
update habits set color=0 where color<0 or color>12;
|
||||
@@ -16,24 +16,24 @@
|
||||
|
||||
package com.android.colorpicker;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
/**
|
||||
* A dialog which takes in as input an array of palette and creates a palette allowing the user to
|
||||
* select a specific color swatch, which invokes a listener.
|
||||
*/
|
||||
public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
|
||||
public class ColorPickerDialog extends AppCompatDialogFragment implements OnColorSelectedListener {
|
||||
|
||||
public static final int SIZE_LARGE = 1;
|
||||
public static final int SIZE_SMALL = 2;
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.util.Log;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
@@ -49,7 +50,7 @@ import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListene
|
||||
/**
|
||||
* Dialog to set a time.
|
||||
*/
|
||||
public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
|
||||
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener{
|
||||
private static final String TAG = "TimePickerDialog";
|
||||
|
||||
private static final String KEY_HOUR_OF_DAY = "hour_of_day";
|
||||
|
||||
@@ -19,33 +19,27 @@
|
||||
|
||||
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.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
public class AboutActivity extends Activity implements View.OnClickListener
|
||||
public class AboutActivity extends BaseActivity 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);
|
||||
}
|
||||
setContentView(R.layout.about);
|
||||
setupSupportActionBar(true);
|
||||
|
||||
int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor);
|
||||
setupActionBarColor(color);
|
||||
|
||||
TextView tvVersion = (TextView) findViewById(R.id.tvVersion);
|
||||
TextView tvRate = (TextView) findViewById(R.id.tvRate);
|
||||
|
||||
@@ -19,17 +19,25 @@
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
abstract public class BaseActivity extends Activity implements Thread.UncaughtExceptionHandler
|
||||
abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler
|
||||
{
|
||||
private static int MAX_UNDO_LEVEL = 15;
|
||||
|
||||
@@ -44,6 +52,8 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
UIHelper.applyCurrentTheme(this);
|
||||
|
||||
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
|
||||
@@ -117,6 +127,23 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
|
||||
showToast(command.getExecuteStringId());
|
||||
}
|
||||
|
||||
protected void setupSupportActionBar(boolean homeButtonEnabled)
|
||||
{
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if(toolbar == null) return;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
toolbar.setElevation(UIHelper.dpToPixels(this, 2));
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
if(homeButtonEnabled)
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
}
|
||||
@@ -127,7 +154,7 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
|
||||
try
|
||||
{
|
||||
ex.printStackTrace();
|
||||
HabitsApplication.generateLogFile();
|
||||
HabitsApplication.dumpBugReportToFile();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -139,4 +166,39 @@ abstract public class BaseActivity extends Activity implements Thread.UncaughtEx
|
||||
else
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
protected void setupActionBarColor(int color)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
if (!UIHelper.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return;
|
||||
|
||||
ColorDrawable drawable = new ColorDrawable(color);
|
||||
actionBar.setBackgroundDrawable(drawable);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f);
|
||||
getWindow().setStatusBarColor(darkerColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume()
|
||||
{
|
||||
super.onPostResume();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
hideFakeToolbarShadow();
|
||||
}
|
||||
|
||||
protected void hideFakeToolbarShadow()
|
||||
{
|
||||
View view = findViewById(R.id.toolbarShadow);
|
||||
if(view != null) view.setVisibility(View.GONE);
|
||||
|
||||
view = findViewById(R.id.headerShadow);
|
||||
if(view != null) view.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
@@ -58,7 +58,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
case ACTION_SHOW_REMINDER:
|
||||
createNotification(context, intent);
|
||||
createReminderAlarms(context);
|
||||
createReminderAlarmsDelayed(context);
|
||||
break;
|
||||
|
||||
case ACTION_DISMISS:
|
||||
@@ -72,10 +72,14 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
case ACTION_SNOOZE:
|
||||
snoozeHabit(context, intent);
|
||||
break;
|
||||
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
ReminderHelper.createReminderAlarms(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createReminderAlarms(final Context context)
|
||||
private void createReminderAlarmsDelayed(final Context context)
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@@ -163,27 +167,28 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
if (todayValue != Checkmark.UNCHECKED) return;
|
||||
if (!checkWeekday(intent, habit)) return;
|
||||
|
||||
// Check if reminder has been turned off after alarm was scheduled
|
||||
if (habit.reminderHour == null) return;
|
||||
if (!habit.hasReminder()) return;
|
||||
|
||||
Intent contentIntent = new Intent(context, MainActivity.class);
|
||||
contentIntent.setData(data);
|
||||
PendingIntent contentPendingIntent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
PendingIntent.getActivity(context, 0, contentIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
PendingIntent dismissPendingIntent = buildDismissIntent(context);
|
||||
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
|
||||
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
|
||||
|
||||
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
Uri ringtoneUri = ReminderHelper.getRingtoneUri(context);
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender =
|
||||
new NotificationCompat.WearableExtender().setBackground(
|
||||
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
|
||||
BitmapFactory.decodeResource(context.getResources(),
|
||||
R.drawable.stripe));
|
||||
|
||||
Notification notification =
|
||||
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
|
||||
new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(habit.name)
|
||||
.setContentText(habit.description)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
@@ -192,7 +197,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
context.getString(R.string.check), checkIntentPending)
|
||||
.addAction(R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze), snoozeIntentPending)
|
||||
.setSound(soundUri)
|
||||
.setSound(ringtoneUri)
|
||||
.extend(wearableExtender)
|
||||
.setWhen(reminderTime)
|
||||
.setShowWhen(true)
|
||||
@@ -201,7 +206,8 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.notify(notificationId, notification);
|
||||
@@ -217,7 +223,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
snoozeIntent.setData(data);
|
||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
|
||||
@@ -227,21 +233,26 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
checkIntent.setData(data);
|
||||
checkIntent.setAction(ACTION_CHECK);
|
||||
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
||||
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
return PendingIntent.getBroadcast(context, 0, checkIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildDismissIntent(Context context)
|
||||
{
|
||||
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
deleteIntent.setAction(ACTION_DISMISS);
|
||||
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
||||
return PendingIntent.getBroadcast(context, 0, deleteIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildViewHabitIntent(Context context, Habit habit)
|
||||
{
|
||||
Intent intent = new Intent(context, ShowHabitActivity.class);
|
||||
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
|
||||
return PendingIntent.getActivity(context, 0, intent, 0);
|
||||
|
||||
return TaskStackBuilder.create(context.getApplicationContext())
|
||||
.addNextIntentWithParentStack(intent)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private boolean checkWeekday(Intent intent, Habit habit)
|
||||
@@ -254,4 +265,13 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
return reminderDays[weekday];
|
||||
}
|
||||
|
||||
public static void dismissNotification(Context context, Habit habit)
|
||||
{
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class HabitsApplication extends Application
|
||||
{
|
||||
@@ -87,6 +88,7 @@ public class HabitsApplication extends Application
|
||||
|
||||
public static String getLogcat() throws IOException
|
||||
{
|
||||
int maxNLines = 250;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] command = new String[] { "logcat", "-d"};
|
||||
@@ -95,10 +97,18 @@ public class HabitsApplication extends Application
|
||||
InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(in);
|
||||
|
||||
LinkedList<String> log = new LinkedList<>();
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null)
|
||||
{
|
||||
builder.append(line);
|
||||
log.addLast(line);
|
||||
if(log.size() > maxNLines) log.removeFirst();
|
||||
}
|
||||
|
||||
for(String l : log)
|
||||
{
|
||||
builder.append(l);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
@@ -130,10 +140,8 @@ public class HabitsApplication extends Application
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File generateLogFile() throws IOException
|
||||
public static File dumpBugReportToFile() throws IOException
|
||||
{
|
||||
String logcat = getLogcat();
|
||||
String deviceInfo = getDeviceInfo();
|
||||
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
|
||||
|
||||
if(context == null) throw new RuntimeException("application context should not be null");
|
||||
@@ -142,10 +150,17 @@ public class HabitsApplication extends Application
|
||||
|
||||
File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date));
|
||||
FileWriter output = new FileWriter(logFile);
|
||||
output.write(deviceInfo);
|
||||
output.write(logcat);
|
||||
output.write(generateBugReport());
|
||||
output.close();
|
||||
|
||||
return logFile;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String generateBugReport() throws IOException
|
||||
{
|
||||
String logcat = getLogcat();
|
||||
String deviceInfo = getDeviceInfo();
|
||||
return deviceInfo + "\n" + logcat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,6 @@ public class IntroActivity extends AppIntro2
|
||||
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
|
||||
Color.parseColor("#ffa726")));
|
||||
|
||||
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_3),
|
||||
getString(R.string.intro_description_3), R.drawable.intro_icon_3,
|
||||
Color.parseColor("#7cb342")));
|
||||
|
||||
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
|
||||
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
|
||||
Color.parseColor("#9575cd")));
|
||||
|
||||
@@ -27,19 +27,24 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.fragments.ListHabitsFragment;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
|
||||
@@ -48,7 +53,6 @@ import org.isoron.uhabits.widgets.HistoryWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.ScoreWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.StreakWidgetProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainActivity extends BaseActivity
|
||||
@@ -70,19 +74,35 @@ public class MainActivity extends BaseActivity
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.list_habits_activity);
|
||||
|
||||
setupSupportActionBar(false);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
listHabitsFragment =
|
||||
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
|
||||
(ListHabitsFragment) getSupportFragmentManager().findFragmentById(R.id.fragment1);
|
||||
|
||||
receiver = new Receiver();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
onPreLollipopStartup();
|
||||
|
||||
onStartup();
|
||||
}
|
||||
|
||||
private void onPreLollipopStartup()
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
if(UIHelper.isNightMode()) return;
|
||||
|
||||
int color = getResources().getColor(R.color.grey_900);
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(color));
|
||||
}
|
||||
|
||||
private void onStartup()
|
||||
{
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
@@ -121,7 +141,12 @@ public class MainActivity extends BaseActivity
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
menu.clear();
|
||||
getMenuInflater().inflate(R.menu.list_habits_menu, menu);
|
||||
|
||||
MenuItem nightModeItem = menu.findItem(R.id.action_night_mode);
|
||||
nightModeItem.setChecked(UIHelper.isNightMode());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,6 +155,17 @@ public class MainActivity extends BaseActivity
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_night_mode:
|
||||
{
|
||||
if(UIHelper.isNightMode())
|
||||
UIHelper.setCurrentTheme(UIHelper.THEME_LIGHT);
|
||||
else
|
||||
UIHelper.setCurrentTheme(UIHelper.THEME_DARK);
|
||||
|
||||
refreshTheme();
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_settings:
|
||||
{
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
@@ -158,6 +194,23 @@ public class MainActivity extends BaseActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshTheme()
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Intent intent = new Intent(MainActivity.this, MainActivity.class);
|
||||
|
||||
MainActivity.this.finish();
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
startActivity(intent);
|
||||
|
||||
}
|
||||
}, 500); // Let the menu disappear first
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
@@ -185,12 +238,25 @@ public class MainActivity extends BaseActivity
|
||||
{
|
||||
try
|
||||
{
|
||||
File logFile = HabitsApplication.generateLogFile();
|
||||
HabitsApplication.dumpBugReportToFile();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String log = "---------- BUG REPORT BEGINS ----------\n";
|
||||
log += HabitsApplication.generateBugReport();
|
||||
log += "---------- BUG REPORT ENDS ------------\n";
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse(getString(R.string.bugReportURL)));
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(logFile));
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("message/rfc822");
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { "dev@loophabits.org" });
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, "Bug Report - Loop Habit Tracker");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||
startActivity(intent);
|
||||
}
|
||||
catch (IOException e)
|
||||
@@ -218,11 +284,21 @@ public class MainActivity extends BaseActivity
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
dismissNotifications(MainActivity.this);
|
||||
updateWidgets(MainActivity.this);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void dismissNotifications(Context context)
|
||||
{
|
||||
for(Habit h : Habit.getHabitsWithReminder())
|
||||
{
|
||||
if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED)
|
||||
HabitBroadcastReceiver.dismissNotification(context, h);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context)
|
||||
{
|
||||
updateWidgets(context, CheckmarkWidgetProvider.class);
|
||||
|
||||
@@ -19,19 +19,20 @@
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.isoron.uhabits.fragments.SettingsFragment;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
public class SettingsActivity extends Activity
|
||||
public class SettingsActivity extends BaseActivity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
setContentView(R.layout.settings_activity);
|
||||
setupSupportActionBar(true);
|
||||
|
||||
int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor);
|
||||
setupActionBarColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.ContentUris;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class ShowHabitActivity extends BaseActivity
|
||||
@@ -38,16 +38,23 @@ public class ShowHabitActivity extends BaseActivity
|
||||
|
||||
Uri data = getIntent().getData();
|
||||
habit = Habit.get(ContentUris.parseId(data));
|
||||
ActionBar actionBar = getActionBar();
|
||||
|
||||
if(actionBar != null && getHabit() != null)
|
||||
{
|
||||
actionBar.setTitle(getHabit().name);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(getHabit().color));
|
||||
}
|
||||
|
||||
setContentView(R.layout.show_habit_activity);
|
||||
|
||||
setupSupportActionBar(true);
|
||||
setupHabitActionBar();
|
||||
}
|
||||
|
||||
private void setupHabitActionBar()
|
||||
{
|
||||
if(habit == null) return;
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
actionBar.setTitle(habit.name);
|
||||
|
||||
setupActionBarColor(ColorHelper.getColor(this, habit.color));
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -40,19 +40,18 @@ import com.android.colorpicker.ColorPickerSwatch;
|
||||
import com.android.datetimepicker.time.RadialPickerLayout;
|
||||
import com.android.datetimepicker.time.TimePickerDialog;
|
||||
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EditHabitFragment extends DialogFragment
|
||||
public class EditHabitDialogFragment extends AppCompatDialogFragment
|
||||
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
|
||||
TimePickerDialog.OnTimeSetListener, Spinner.OnItemSelectedListener
|
||||
{
|
||||
@@ -79,9 +78,9 @@ public class EditHabitFragment extends DialogFragment
|
||||
private SharedPreferences prefs;
|
||||
private boolean is24HourMode;
|
||||
|
||||
public static EditHabitFragment editSingleHabitFragment(long id)
|
||||
public static EditHabitDialogFragment editSingleHabitFragment(long id)
|
||||
{
|
||||
EditHabitFragment frag = new EditHabitFragment();
|
||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("habitId", id);
|
||||
args.putInt("editMode", EDIT_MODE);
|
||||
@@ -89,9 +88,9 @@ public class EditHabitFragment extends DialogFragment
|
||||
return frag;
|
||||
}
|
||||
|
||||
public static EditHabitFragment createHabitFragment()
|
||||
public static EditHabitDialogFragment createHabitFragment()
|
||||
{
|
||||
EditHabitFragment frag = new EditHabitFragment();
|
||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("editMode", CREATE_MODE);
|
||||
frag.setArguments(args);
|
||||
@@ -138,7 +137,7 @@ public class EditHabitFragment extends DialogFragment
|
||||
modifiedHabit = new Habit();
|
||||
modifiedHabit.freqNum = 1;
|
||||
modifiedHabit.freqDen = 1;
|
||||
modifiedHabit.color = prefs.getInt("pref_default_habit_color", modifiedHabit.color);
|
||||
modifiedHabit.color = prefs.getInt("pref_default_habit_palette_color", modifiedHabit.color);
|
||||
}
|
||||
else if (mode == EDIT_MODE)
|
||||
{
|
||||
@@ -174,13 +173,13 @@ public class EditHabitFragment extends DialogFragment
|
||||
return view;
|
||||
}
|
||||
|
||||
private void changeColor(Integer color)
|
||||
private void changeColor(int paletteColor)
|
||||
{
|
||||
modifiedHabit.color = color;
|
||||
tvName.setTextColor(color);
|
||||
modifiedHabit.color = paletteColor;
|
||||
tvName.setTextColor(ColorHelper.getColor(getActivity(), paletteColor));
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt("pref_default_habit_color", color);
|
||||
editor.putInt("pref_default_habit_palette_color", paletteColor);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@@ -237,15 +236,18 @@ public class EditHabitFragment extends DialogFragment
|
||||
|
||||
private void onColorButtonClick()
|
||||
{
|
||||
int originalAndroidColor = ColorHelper.getColor(getActivity(), modifiedHabit.color);
|
||||
|
||||
ColorPickerDialog picker = ColorPickerDialog.newInstance(
|
||||
R.string.color_picker_default_title, ColorHelper.palette, modifiedHabit.color, 4,
|
||||
ColorPickerDialog.SIZE_SMALL);
|
||||
R.string.color_picker_default_title, ColorHelper.getPalette(getActivity()),
|
||||
originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int color)
|
||||
public void onColorSelected(int androidColor)
|
||||
{
|
||||
changeColor(color);
|
||||
int paletteColor = ColorHelper.colorToPaletteIndex(getActivity(), androidColor);
|
||||
changeColor(paletteColor);
|
||||
}
|
||||
});
|
||||
picker.show(getFragmentManager(), "picker");
|
||||
@@ -19,21 +19,20 @@
|
||||
|
||||
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.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
|
||||
public class HistoryEditorDialog extends DialogFragment
|
||||
public class HistoryEditorDialog extends AppCompatDialogFragment
|
||||
implements DialogInterface.OnClickListener
|
||||
{
|
||||
private Habit habit;
|
||||
@@ -89,8 +88,6 @@ public class HistoryEditorDialog extends DialogFragment
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
|
||||
public class WeekdayPickerDialog extends DialogFragment
|
||||
public class WeekdayPickerDialog extends AppCompatDialogFragment
|
||||
implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener
|
||||
{
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ 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.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ListHabitsHelper;
|
||||
import org.isoron.uhabits.loaders.HabitListLoader;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -74,28 +72,15 @@ class HabitListAdapter extends BaseAdapter
|
||||
public View getView(int position, View view, ViewGroup parent)
|
||||
{
|
||||
final Habit habit = loader.habitsList.get(position);
|
||||
boolean selected = selectedPositions.contains(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);
|
||||
view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener,
|
||||
onCheckmarkClickListener);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
helper.updateHabitCard(view, habit, selected);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.ActionMode;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ProgressBar;
|
||||
@@ -29,12 +29,13 @@ import android.widget.ProgressBar;
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.BaseActivity;
|
||||
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.dialogs.EditHabitDialogFragment;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.loaders.HabitListLoader;
|
||||
@@ -155,27 +156,33 @@ public class HabitSelectionCallback implements ActionMode.Callback
|
||||
|
||||
case R.id.action_edit_habit:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId());
|
||||
EditHabitDialogFragment
|
||||
frag = EditHabitDialogFragment.editSingleHabitFragment(firstHabit.getId());
|
||||
frag.setOnSavedListener(onSavedListener);
|
||||
frag.show(activity.getFragmentManager(), "editHabit");
|
||||
frag.show(activity.getSupportFragmentManager(), "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);
|
||||
int originalAndroidColor = ColorHelper.getColor(activity, firstHabit.color);
|
||||
|
||||
ColorPickerDialog picker = ColorPickerDialog.newInstance(
|
||||
R.string.color_picker_default_title, ColorHelper.getPalette(activity),
|
||||
originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int color)
|
||||
public void onColorSelected(int androidColor)
|
||||
{
|
||||
activity.executeCommand(
|
||||
new ChangeHabitColorCommand(selectedHabits, color), null);
|
||||
int paletteColor = ColorHelper.colorToPaletteIndex(activity,
|
||||
androidColor);
|
||||
activity.executeCommand(new ChangeHabitColorCommand(selectedHabits,
|
||||
paletteColor), null);
|
||||
mode.finish();
|
||||
}
|
||||
});
|
||||
picker.show(activity.getFragmentManager(), "picker");
|
||||
picker.show(activity.getSupportFragmentManager(), "picker");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,58 +19,35 @@
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.net.*;
|
||||
import android.os.*;
|
||||
import android.preference.*;
|
||||
import android.support.annotation.*;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.view.*;
|
||||
import android.view.ContextMenu.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
import android.widget.AdapterView.*;
|
||||
|
||||
import com.mobeta.android.dslv.DragSortController;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
import com.mobeta.android.dslv.DragSortListView.DropListener;
|
||||
import com.mobeta.android.dslv.*;
|
||||
import com.mobeta.android.dslv.DragSortListView.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.BaseActivity;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||
import org.isoron.uhabits.dialogs.FilePickerDialog;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.helpers.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 org.isoron.uhabits.tasks.ExportCSVTask;
|
||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||
import org.isoron.uhabits.commands.*;
|
||||
import org.isoron.uhabits.dialogs.*;
|
||||
import org.isoron.uhabits.helpers.*;
|
||||
import org.isoron.uhabits.helpers.UIHelper.*;
|
||||
import org.isoron.uhabits.loaders.*;
|
||||
import org.isoron.uhabits.models.*;
|
||||
import org.isoron.uhabits.tasks.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class ListHabitsFragment extends Fragment
|
||||
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
||||
@@ -120,7 +97,7 @@ public class ListHabitsFragment extends Fragment
|
||||
loader.setCheckmarkCount(helper.getButtonCount());
|
||||
|
||||
llHint.setOnClickListener(this);
|
||||
tvStarEmpty.setTypeface(helper.getFontawesome());
|
||||
tvStarEmpty.setTypeface(UIHelper.getFontAwesome(activity));
|
||||
|
||||
adapter = new HabitListAdapter(getActivity(), loader);
|
||||
adapter.setSelectedPositions(selectedPositions);
|
||||
@@ -141,13 +118,11 @@ public class ListHabitsFragment extends Fragment
|
||||
|
||||
if(savedInstanceState != null)
|
||||
{
|
||||
EditHabitFragment frag = (EditHabitFragment) getFragmentManager()
|
||||
EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager()
|
||||
.findFragmentByTag("editHabit");
|
||||
if(frag != null) frag.setOnSavedListener(this);
|
||||
}
|
||||
|
||||
loader.updateAllHabits(true);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
}
|
||||
@@ -167,11 +142,8 @@ public class ListHabitsFragment extends Fragment
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
Long timestamp = loader.getLastLoadTimestamp();
|
||||
|
||||
if (timestamp != null && timestamp != DateHelper.getStartOfToday())
|
||||
loader.updateAllHabits(true);
|
||||
|
||||
loader.updateAllHabits(true);
|
||||
helper.updateEmptyMessage(llEmpty);
|
||||
helper.updateHeader(llButtonsHeader);
|
||||
hintManager.showHintIfAppropriate();
|
||||
@@ -217,7 +189,7 @@ public class ListHabitsFragment extends Fragment
|
||||
{
|
||||
case R.id.action_add:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
|
||||
EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment();
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "editHabit");
|
||||
return true;
|
||||
@@ -284,7 +256,7 @@ public class ListHabitsFragment extends Fragment
|
||||
callback.setOnSavedListener(this);
|
||||
callback.setListener(this);
|
||||
|
||||
actionMode = getActivity().startActionMode(callback);
|
||||
actionMode = activity.startSupportActionMode(callback);
|
||||
}
|
||||
|
||||
if(actionMode != null) actionMode.invalidate();
|
||||
@@ -328,17 +300,18 @@ public class ListHabitsFragment extends Fragment
|
||||
|
||||
private void toggleCheck(View v)
|
||||
{
|
||||
Long tag = (Long) v.getTag(R.string.habit_key);
|
||||
Integer offset = (Integer) v.getTag(R.string.offset_key);
|
||||
long timestamp = DateHelper.getStartOfDay(
|
||||
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
|
||||
|
||||
Habit habit = loader.habits.get(tag);
|
||||
Long id = helper.getHabitIdFromCheckmarkView(v);
|
||||
Habit habit = loader.habits.get(id);
|
||||
if(habit == null) return;
|
||||
|
||||
listView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX();
|
||||
float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY();
|
||||
helper.triggerRipple((View) v.getParent().getParent(), x, y);
|
||||
|
||||
listView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
helper.toggleCheckmarkView(v, habit);
|
||||
|
||||
long timestamp = helper.getTimestampFromCheckmarkView(v);
|
||||
executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId());
|
||||
}
|
||||
|
||||
@@ -380,6 +353,7 @@ public class ListHabitsFragment extends Fragment
|
||||
else loader.updateHabit(refreshKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionModeDestroyed(ActionMode mode)
|
||||
{
|
||||
actionMode = null;
|
||||
|
||||
@@ -20,19 +20,23 @@
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
|
||||
import org.isoron.uhabits.MainActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
private static int RINGTONE_REQUEST_CODE = 1;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
@@ -44,10 +48,18 @@ public class SettingsFragment extends PreferenceFragment
|
||||
setResultOnPreferenceClick("exportDB", MainActivity.RESULT_EXPORT_DB);
|
||||
setResultOnPreferenceClick("bugReport", MainActivity.RESULT_BUG_REPORT);
|
||||
|
||||
updateRingtoneDescription();
|
||||
|
||||
if(UIHelper.isLocaleFullyTranslated())
|
||||
removePreference("translate", "linksCategory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void removePreference(String preferenceKey, String categoryKey)
|
||||
{
|
||||
PreferenceCategory cat = (PreferenceCategory) findPreference(categoryKey);
|
||||
@@ -91,4 +103,39 @@ public class SettingsFragment extends PreferenceFragment
|
||||
{
|
||||
BackupManager.dataChanged("org.isoron.uhabits");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference)
|
||||
{
|
||||
if(preference.getKey() == null) return false;
|
||||
|
||||
if (preference.getKey().equals("reminderSound"))
|
||||
{
|
||||
ReminderHelper.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
if(requestCode == RINGTONE_REQUEST_CODE)
|
||||
{
|
||||
ReminderHelper.parseRingtoneData(getContext(), data);
|
||||
updateRingtoneDescription();
|
||||
return;
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
private void updateRingtoneDescription()
|
||||
{
|
||||
String ringtoneName = ReminderHelper.getRingtoneName(getContext());
|
||||
if(ringtoneName == null) return;
|
||||
Preference ringtonePreference = findPreference("reminderSound");
|
||||
ringtonePreference.setSummary(ringtoneName);
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,9 @@
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -40,8 +37,10 @@ import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.ShowHabitActivity;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.dialogs.EditHabitDialogFragment;
|
||||
import org.isoron.uhabits.dialogs.HistoryEditorDialog;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -73,11 +72,15 @@ public class ShowHabitFragment extends Fragment
|
||||
@Nullable
|
||||
private HabitScoreView scoreView;
|
||||
|
||||
@Nullable
|
||||
private SharedPreferences prefs;
|
||||
|
||||
private int previousScoreInterval;
|
||||
|
||||
private float todayScore;
|
||||
private float lastMonthScore;
|
||||
private float lastYearScore;
|
||||
|
||||
private int activeColor;
|
||||
private int inactiveColor;
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
@@ -90,7 +93,12 @@ public class ShowHabitFragment extends Fragment
|
||||
{
|
||||
View view = inflater.inflate(R.layout.show_habit, container, false);
|
||||
activity = (ShowHabitActivity) getActivity();
|
||||
|
||||
habit = activity.getHabit();
|
||||
activeColor = ColorHelper.getColor(getContext(), habit.color);
|
||||
inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
||||
|
||||
updateHeader(view);
|
||||
|
||||
dataViews = new LinkedList<>();
|
||||
|
||||
@@ -99,13 +107,10 @@ public class ShowHabitFragment extends Fragment
|
||||
|
||||
scoreView = (HabitScoreView) view.findViewById(R.id.scoreView);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
|
||||
if(defaultScoreInterval > 5 || defaultScoreInterval < 0) defaultScoreInterval = 1;
|
||||
int defaultScoreInterval = UIHelper.getDefaultScoreInterval(getContext());
|
||||
previousScoreInterval = defaultScoreInterval;
|
||||
setScoreBucketSize(defaultScoreInterval);
|
||||
|
||||
|
||||
sStrengthInterval.setSelection(defaultScoreInterval);
|
||||
sStrengthInterval.setOnItemSelectedListener(this);
|
||||
|
||||
@@ -133,7 +138,7 @@ public class ShowHabitFragment extends Fragment
|
||||
|
||||
if(savedInstanceState != null)
|
||||
{
|
||||
EditHabitFragment fragEdit = (EditHabitFragment) getFragmentManager()
|
||||
EditHabitDialogFragment fragEdit = (EditHabitDialogFragment) getFragmentManager()
|
||||
.findFragmentByTag("editHabit");
|
||||
HistoryEditorDialog fragEditor = (HistoryEditorDialog) getFragmentManager()
|
||||
.findFragmentByTag("historyEditor");
|
||||
@@ -147,6 +152,56 @@ public class ShowHabitFragment extends Fragment
|
||||
return view;
|
||||
}
|
||||
|
||||
private void updateHeader(View view)
|
||||
{
|
||||
if(habit == null) return;
|
||||
|
||||
TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel);
|
||||
questionLabel.setTextColor(activeColor);
|
||||
questionLabel.setText(habit.description);
|
||||
|
||||
TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel);
|
||||
if(habit.hasReminder())
|
||||
reminderLabel.setText(DateHelper.formatTime(getActivity(), habit.reminderHour,
|
||||
habit.reminderMin));
|
||||
else
|
||||
reminderLabel.setText(getResources().getString(R.string.reminder_off));
|
||||
|
||||
TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel);
|
||||
frequencyLabel.setText(getFreqText());
|
||||
|
||||
if(habit.description.isEmpty())
|
||||
questionLabel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private String getFreqText()
|
||||
{
|
||||
if(habit == null)
|
||||
return "";
|
||||
|
||||
if(habit.freqNum.equals(habit.freqDen))
|
||||
return getResources().getString(R.string.every_day);
|
||||
|
||||
if(habit.freqNum == 1)
|
||||
{
|
||||
if (habit.freqDen == 7)
|
||||
return getResources().getString(R.string.every_week);
|
||||
|
||||
if (habit.freqDen % 7 == 0)
|
||||
return getResources().getString(R.string.every_x_weeks, habit.freqDen / 7);
|
||||
|
||||
return getResources().getString(R.string.every_x_days, habit.freqDen);
|
||||
}
|
||||
|
||||
String times_every = getResources().getString(R.string.times_every);
|
||||
|
||||
if(habit.freqNum == 1)
|
||||
times_every = getResources().getString(R.string.time_every);
|
||||
|
||||
return String.format("%d %s %d %s", habit.freqNum, times_every, habit.freqDen,
|
||||
getResources().getString(R.string.days));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
@@ -154,42 +209,53 @@ public class ShowHabitFragment extends Fragment
|
||||
refreshData();
|
||||
}
|
||||
|
||||
private void updateScoreRing(View view)
|
||||
private void updateScore(View view)
|
||||
{
|
||||
if(habit == null) return;
|
||||
if(view == null) return;
|
||||
|
||||
float todayValue = (float) habit.scores.getTodayValue();
|
||||
float percentage = todayValue / Score.MAX_VALUE;
|
||||
float todayPercentage = todayScore / Score.MAX_VALUE;
|
||||
float monthDiff = todayPercentage - (lastMonthScore / Score.MAX_VALUE);
|
||||
float yearDiff = todayPercentage - (lastYearScore / Score.MAX_VALUE);
|
||||
|
||||
RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing);
|
||||
scoreRing.setColor(habit.color);
|
||||
scoreRing.setPercentage(percentage);
|
||||
int androidColor = ColorHelper.getColor(getActivity(), habit.color);
|
||||
scoreRing.setColor(androidColor);
|
||||
scoreRing.setPercentage(todayPercentage);
|
||||
|
||||
TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel);
|
||||
TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel);
|
||||
TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel);
|
||||
|
||||
scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100));
|
||||
|
||||
String minus = "\u2212";
|
||||
monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus),
|
||||
Math.abs(monthDiff) * 100));
|
||||
yearDiffLabel.setText(
|
||||
String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100));
|
||||
|
||||
monthDiffLabel.setTextColor(monthDiff >= 0 ? activeColor : inactiveColor);
|
||||
yearDiffLabel.setTextColor(yearDiff >= 0 ? activeColor : inactiveColor);
|
||||
}
|
||||
|
||||
private void updateHeaders(View view)
|
||||
{
|
||||
if(habit == null | activity == null) return;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
|
||||
activity.getWindow().setStatusBarColor(darkerHabitColor);
|
||||
}
|
||||
|
||||
updateColor(view, R.id.tvHistory);
|
||||
updateColor(view, R.id.tvOverview);
|
||||
updateColor(view, R.id.tvStrength);
|
||||
updateColor(view, R.id.tvStreaks);
|
||||
updateColor(view, R.id.tvWeekdayFreq);
|
||||
updateColor(view, R.id.scoreLabel);
|
||||
}
|
||||
|
||||
private void updateColor(View view, int viewId)
|
||||
{
|
||||
if(habit == null) return;
|
||||
if(habit == null || activity == null) return;
|
||||
|
||||
TextView textView = (TextView) view.findViewById(viewId);
|
||||
textView.setTextColor(habit.color);
|
||||
int androidColor = ColorHelper.getColor(activity, habit.color);
|
||||
textView.setTextColor(androidColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,7 +273,8 @@ public class ShowHabitFragment extends Fragment
|
||||
{
|
||||
case R.id.action_edit_habit:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
|
||||
EditHabitDialogFragment
|
||||
frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId());
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "editHabit");
|
||||
return true;
|
||||
@@ -227,6 +294,8 @@ public class ShowHabitFragment extends Fragment
|
||||
else activity.executeCommand(command, h.getId());
|
||||
|
||||
ReminderHelper.createReminderAlarms(activity);
|
||||
HabitBroadcastReceiver.sendRefreshBroadcast(getActivity());
|
||||
|
||||
activity.recreate();
|
||||
}
|
||||
|
||||
@@ -241,25 +310,32 @@ public class ShowHabitFragment extends Fragment
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
float percentage;
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
if(habit == null) return;
|
||||
if(dataViews == null) return;
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long lastMonth = today - 30 * DateHelper.millisecondsInOneDay;
|
||||
long lastYear = today - 365 * DateHelper.millisecondsInOneDay;
|
||||
|
||||
todayScore = (float) habit.scores.getTodayValue();
|
||||
lastMonthScore = (float) habit.scores.getValue(lastMonth);
|
||||
lastYearScore = (float) habit.scores.getValue(lastYear);
|
||||
|
||||
int count = 0;
|
||||
for(HabitDataView view : dataViews)
|
||||
{
|
||||
view.refreshData();
|
||||
onProgressUpdate(count++);
|
||||
publishProgress(count++);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values)
|
||||
{
|
||||
updateScoreRing(getView());
|
||||
updateScore(getView());
|
||||
if(dataViews == null) return;
|
||||
dataViews.get(values[0]).postInvalidate();
|
||||
}
|
||||
@@ -278,15 +354,15 @@ public class ShowHabitFragment extends Fragment
|
||||
{
|
||||
if(scoreView == null) return;
|
||||
|
||||
int sizes[] = { 1, 7, 31, 92, 365 };
|
||||
int size = sizes[position];
|
||||
scoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]);
|
||||
|
||||
scoreView.setBucketSize(size);
|
||||
if(position != previousScoreInterval) refreshData();
|
||||
|
||||
if(prefs != null)
|
||||
prefs.edit().putInt("pref_score_view_interval", position).apply();
|
||||
if(position != previousScoreInterval)
|
||||
{
|
||||
refreshData();
|
||||
HabitBroadcastReceiver.sendRefreshBroadcast(getActivity());
|
||||
}
|
||||
|
||||
UIHelper.setDefaultScoreInterval(getContext(), position);
|
||||
previousScoreInterval = position;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,27 +19,63 @@
|
||||
|
||||
package org.isoron.uhabits.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
public class ColorHelper
|
||||
{
|
||||
public static final int[] palette =
|
||||
public static int CSV_PALETTE[] =
|
||||
{
|
||||
Color.parseColor("#D32F2F"), // red
|
||||
Color.parseColor("#E64A19"), // orange
|
||||
Color.parseColor("#F9A825"), // yellow
|
||||
Color.parseColor("#AFB42B"), // light green
|
||||
Color.parseColor("#388E3C"), // dark green
|
||||
Color.parseColor("#00897B"), // teal
|
||||
Color.parseColor("#00ACC1"), // cyan
|
||||
Color.parseColor("#039BE5"), // blue
|
||||
Color.parseColor("#5E35B1"), // deep purple
|
||||
Color.parseColor("#8E24AA"), // purple
|
||||
Color.parseColor("#D81B60"), // pink
|
||||
Color.parseColor("#303030"), // dark grey
|
||||
Color.parseColor("#aaaaaa") // light grey
|
||||
Color.parseColor("#D32F2F"), // 0 red
|
||||
Color.parseColor("#E64A19"), // 1 orange
|
||||
Color.parseColor("#F9A825"), // 2 yellow
|
||||
Color.parseColor("#AFB42B"), // 3 light green
|
||||
Color.parseColor("#388E3C"), // 4 dark green
|
||||
Color.parseColor("#00897B"), // 5 teal
|
||||
Color.parseColor("#00ACC1"), // 6 cyan
|
||||
Color.parseColor("#039BE5"), // 7 blue
|
||||
Color.parseColor("#5E35B1"), // 8 deep purple
|
||||
Color.parseColor("#8E24AA"), // 9 purple
|
||||
Color.parseColor("#D81B60"), // 10 pink
|
||||
Color.parseColor("#303030"), // 11 dark grey
|
||||
Color.parseColor("#aaaaaa") // 12 light grey
|
||||
};
|
||||
|
||||
public static int colorToPaletteIndex(Context context, int color)
|
||||
{
|
||||
int[] palette = getPalette(context);
|
||||
|
||||
for(int k = 0; k < palette.length; k++)
|
||||
if(palette[k] == color) return k;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int[] getPalette(Context context)
|
||||
{
|
||||
int resourceId = UIHelper.getStyleResource(context, R.attr.palette);
|
||||
if(resourceId < 0) return CSV_PALETTE;
|
||||
|
||||
return context.getResources().getIntArray(resourceId);
|
||||
}
|
||||
|
||||
public static int getColor(Context context, int paletteColor)
|
||||
{
|
||||
if(context == null) throw new IllegalArgumentException("Context is null");
|
||||
|
||||
int palette[] = getPalette(context);
|
||||
if(paletteColor < 0 || paletteColor >= palette.length)
|
||||
{
|
||||
Log.w("ColorHelper", String.format("Invalid color: %d. Returning default.", paletteColor));
|
||||
paletteColor = 0;
|
||||
}
|
||||
|
||||
return palette[paletteColor];
|
||||
}
|
||||
|
||||
public static int mixColors(int color1, int color2, float amount)
|
||||
{
|
||||
final byte ALPHA_CHANNEL = 24;
|
||||
@@ -76,6 +112,12 @@ public class ColorHelper
|
||||
return setHSVParameter(color, newValue, 2);
|
||||
}
|
||||
|
||||
public static int setAlpha(int color, float newAlpha)
|
||||
{
|
||||
int intAlpha = (int) (newAlpha * 255);
|
||||
return Color.argb(intAlpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
public static int setMinValue(int color, float newValue)
|
||||
{
|
||||
float hsv[] = new float[3];
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.text.format.DateFormat;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
@@ -155,14 +156,26 @@ public class DateHelper
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throughout the code, it is assumed that the weekdays are numbered from 0 (Saturday) to 6
|
||||
* (Friday). In the Java Calendar they are numbered from 1 (Sunday) to 7 (Saturday). This
|
||||
* function converts from Java to our internal representation.
|
||||
*
|
||||
* @return weekday number in the internal interpretation
|
||||
*/
|
||||
public static int javaWeekdayToLoopWeekday(int number)
|
||||
{
|
||||
return number % 7;
|
||||
}
|
||||
|
||||
public static String[] getDayNames(int format)
|
||||
{
|
||||
String[] wdays = new String[7];
|
||||
|
||||
GregorianCalendar day = new GregorianCalendar();
|
||||
day.set(GregorianCalendar.DAY_OF_WEEK, 0);
|
||||
Calendar day = new GregorianCalendar();
|
||||
day.set(GregorianCalendar.DAY_OF_WEEK, Calendar.SATURDAY);
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
for (int i = 0; i < wdays.length; i++)
|
||||
{
|
||||
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
@@ -172,6 +185,43 @@ public class DateHelper
|
||||
return wdays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with weekday names starting according to locale settings,
|
||||
* e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Europe
|
||||
*/
|
||||
public static String[] getLocaleDayNames(int format)
|
||||
{
|
||||
String[] days = new String[7];
|
||||
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < days.length; i++)
|
||||
{
|
||||
days[i] = calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with week days numbers starting according to locale settings,
|
||||
* e.g. [2,3,4,5,6,7,1] in Europe
|
||||
*/
|
||||
public static Integer[] getLocaleWeekdayList()
|
||||
{
|
||||
Integer[] dayNumbers = new Integer[7];
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < dayNumbers.length; i++)
|
||||
{
|
||||
dayNumbers[i] = calendar.get(GregorianCalendar.DAY_OF_WEEK);
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
return dayNumbers;
|
||||
}
|
||||
|
||||
public static String formatWeekdayList(Context context, boolean weekday[])
|
||||
{
|
||||
String shortDayNames[] = getShortDayNames();
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
package org.isoron.uhabits.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -34,43 +35,44 @@ import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.loaders.HabitListLoader;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.views.RingView;
|
||||
|
||||
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 static final int CHECKMARK_LEFT_TO_RIGHT = 0;
|
||||
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
|
||||
|
||||
private final int lowContrastColor;
|
||||
private final int mediumContrastColor;
|
||||
|
||||
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;
|
||||
lowContrastColor = UIHelper.getStyledColor(context, R.attr.lowContrastTextColor);
|
||||
mediumContrastColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
|
||||
}
|
||||
|
||||
public int getButtonCount()
|
||||
{
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
int width = (int) (dm.widthPixels / dm.density);
|
||||
return Math.max(0, (int) ((width - 160) / 42.0));
|
||||
float screenWidth = UIHelper.getScreenWidth(context);
|
||||
float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth);
|
||||
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth));
|
||||
}
|
||||
|
||||
public int getHabitNameWidth()
|
||||
{
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
int width = (int) (dm.widthPixels / dm.density);
|
||||
return (int) ((width - 30 - getButtonCount() * 42) * dm.density);
|
||||
float screenWidth = UIHelper.getScreenWidth(context);
|
||||
float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth);
|
||||
float padding = UIHelper.dpToPixels(context, 15);
|
||||
return (int) (screenWidth - padding - getButtonCount() * buttonWidth);
|
||||
}
|
||||
|
||||
public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons)
|
||||
@@ -83,8 +85,12 @@ public class ListHabitsHelper
|
||||
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
int position = i;
|
||||
|
||||
TextView tvCheck = (TextView) llButtons.getChildAt(i);
|
||||
if(getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT)
|
||||
position = m - i - 1;
|
||||
|
||||
TextView tvCheck = (TextView) llButtons.getChildAt(position);
|
||||
tvCheck.setTag(R.string.habit_key, habitId);
|
||||
tvCheck.setTag(R.string.offset_key, i);
|
||||
if(isChecked.length > i)
|
||||
@@ -94,54 +100,32 @@ public class ListHabitsHelper
|
||||
|
||||
public int getActiveColor(Habit habit)
|
||||
{
|
||||
int activeColor = habit.color;
|
||||
if(habit.isArchived()) activeColor = INACTIVE_COLOR;
|
||||
int activeColor = ColorHelper.getColor(context, habit.color);
|
||||
if(habit.isArchived()) activeColor = mediumContrastColor;
|
||||
|
||||
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)
|
||||
public void updateNameAndIcon(Habit habit, RingView ring, 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());
|
||||
int score = loader.scores.get(habit.getId());
|
||||
float percentage = (float) score / Score.MAX_VALUE;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
ring.setColor(activeColor);
|
||||
ring.setPercentage(percentage);
|
||||
ring.setPrecision(1.0f / 16);
|
||||
}
|
||||
|
||||
public void updateCheckmark(int activeColor, TextView tvCheck, int check)
|
||||
@@ -156,27 +140,64 @@ public class ListHabitsHelper
|
||||
|
||||
case 1:
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 1);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
tvCheck.setText(R.string.fa_times);
|
||||
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
|
||||
tvCheck.setTextColor(lowContrastColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateHabitBackground(View view, boolean isSelected)
|
||||
public View inflateHabitCard(LayoutInflater inflater,
|
||||
View.OnLongClickListener onCheckmarkLongClickListener,
|
||||
View.OnClickListener onCheckmarkClickListener)
|
||||
{
|
||||
if (isSelected)
|
||||
view.setBackgroundResource(R.drawable.selected_box);
|
||||
View view = inflater.inflate(R.layout.list_habits_item, null);
|
||||
initializeLabelAndIcon(view);
|
||||
inflateCheckmarkButtons(view, onCheckmarkLongClickListener, onCheckmarkClickListener,
|
||||
inflater);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void updateHabitCard(View view, Habit habit, boolean selected)
|
||||
{
|
||||
RingView scoreRing = ((RingView) view.findViewById(R.id.scoreRing));
|
||||
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());
|
||||
llInner.setOnTouchListener(new HotspotTouchListener());
|
||||
|
||||
updateNameAndIcon(habit, scoreRing, tvName);
|
||||
updateCheckmarkButtons(habit, llButtons);
|
||||
updateHabitCardBackground(llInner, selected);
|
||||
}
|
||||
|
||||
|
||||
public void updateHabitCardBackground(View view, boolean isSelected)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
if (isSelected)
|
||||
view.setBackgroundResource(R.drawable.selected_box);
|
||||
else
|
||||
view.setBackgroundResource(R.drawable.ripple);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
view.setBackgroundResource(R.drawable.ripple_white);
|
||||
else view.setBackgroundResource(R.drawable.card_background);
|
||||
Drawable background;
|
||||
|
||||
if (isSelected)
|
||||
background = UIHelper.getStyledDrawable(context, R.attr.selectedBackground);
|
||||
else
|
||||
background = UIHelper.getStyledDrawable(context, R.attr.cardBackground);
|
||||
|
||||
view.setBackgroundDrawable(background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +208,7 @@ public class ListHabitsHelper
|
||||
{
|
||||
View check = inflater.inflate(R.layout.list_habits_item_check, null);
|
||||
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck);
|
||||
btCheck.setTypeface(fontawesome);
|
||||
btCheck.setTypeface(UIHelper.getFontAwesome(context));
|
||||
btCheck.setOnLongClickListener(onLongClickListener);
|
||||
btCheck.setOnClickListener(onClickListener);
|
||||
btCheck.setHapticFeedbackEnabled(false);
|
||||
@@ -205,11 +226,15 @@ public class ListHabitsHelper
|
||||
|
||||
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);
|
||||
int position = 0;
|
||||
|
||||
if(getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT)
|
||||
position = i;
|
||||
|
||||
View tvDay = inflater.inflate(R.layout.list_habits_header_check, null);
|
||||
TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck);
|
||||
btCheck.setText(DateHelper.formatHeaderDate(day));
|
||||
header.addView(tvDay, position);
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
}
|
||||
@@ -222,9 +247,59 @@ public class ListHabitsHelper
|
||||
|
||||
public void toggleCheckmarkView(View v, Habit habit)
|
||||
{
|
||||
int androidColor = ColorHelper.getColor(context, habit.color);
|
||||
|
||||
if (v.getTag(R.string.toggle_key).equals(2))
|
||||
updateCheckmark(habit.color, (TextView) v, 0);
|
||||
updateCheckmark(androidColor, (TextView) v, 0);
|
||||
else
|
||||
updateCheckmark(habit.color, (TextView) v, 2);
|
||||
updateCheckmark(androidColor, (TextView) v, 2);
|
||||
}
|
||||
|
||||
public Long getHabitIdFromCheckmarkView(View v)
|
||||
{
|
||||
return (Long) v.getTag(R.string.habit_key);
|
||||
}
|
||||
|
||||
public long getTimestampFromCheckmarkView(View v)
|
||||
{
|
||||
Integer offset = (Integer) v.getTag(R.string.offset_key);
|
||||
return DateHelper.getStartOfDay(DateHelper.getLocalTime() -
|
||||
offset * DateHelper.millisecondsInOneDay);
|
||||
}
|
||||
|
||||
public void triggerRipple(View v, final float x, final float y)
|
||||
{
|
||||
final Drawable background = v.getBackground();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
background.setHotspot(x, y);
|
||||
|
||||
background.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});
|
||||
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
background.setState(new int[]{});
|
||||
}
|
||||
}, 25);
|
||||
}
|
||||
|
||||
private static class HotspotTouchListener implements View.OnTouchListener
|
||||
{
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
v.getBackground().setHotspot(event.getX(), event.getY());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCheckmarkOrder()
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean reverse = prefs.getBoolean("pref_checkmark_reverse_order", false);
|
||||
return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,19 @@ import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.text.DateFormat;
|
||||
@@ -86,7 +93,81 @@ public class ReminderHelper
|
||||
else
|
||||
manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
|
||||
|
||||
String name = habit.name.substring(0, Math.min(3, habit.name.length()));
|
||||
Log.d("ReminderHelper", String.format("Setting alarm (%s): %s",
|
||||
DateFormat.getDateTimeInstance().format(new Date(reminderTime)), habit.name));
|
||||
DateFormat.getDateTimeInstance().format(new Date(reminderTime)), name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Uri getRingtoneUri(Context context)
|
||||
{
|
||||
Uri ringtoneUri = null;
|
||||
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String prefRingtoneUri = prefs.getString("pref_ringtone_uri", defaultRingtoneUri.toString());
|
||||
if (prefRingtoneUri.length() > 0) ringtoneUri = Uri.parse(prefRingtoneUri);
|
||||
|
||||
return ringtoneUri;
|
||||
}
|
||||
|
||||
public static void parseRingtoneData(Context context, @Nullable Intent data)
|
||||
{
|
||||
if(data == null) return;
|
||||
|
||||
Uri ringtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
||||
|
||||
if (ringtoneUri != null)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString("pref_ringtone_uri", ringtoneUri.toString()).apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
String off = context.getResources().getString(R.string.none);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString("pref_ringtone_uri", "").apply();
|
||||
}
|
||||
}
|
||||
|
||||
public static void startRingtonePickerActivity(Fragment fragment, int requestCode)
|
||||
{
|
||||
Uri existingRingtoneUri = ReminderHelper.getRingtoneUri(fragment.getContext());
|
||||
Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
|
||||
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultRingtoneUri);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingRingtoneUri);
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getRingtoneName(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri ringtoneUri = getRingtoneUri(context);
|
||||
String ringtoneName = context.getResources().getString(R.string.none);
|
||||
|
||||
if (ringtoneUri != null)
|
||||
{
|
||||
Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri);
|
||||
if (ringtone != null)
|
||||
{
|
||||
ringtoneName = ringtone.getTitle(context);
|
||||
ringtone.stop();
|
||||
}
|
||||
}
|
||||
|
||||
return ringtoneName;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,37 +19,58 @@
|
||||
|
||||
package org.isoron.uhabits.helpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class UIHelper
|
||||
{
|
||||
|
||||
public static final String ISORON_NAMESPACE = "http://isoron.org/android";
|
||||
private static Typeface fontawesome;
|
||||
|
||||
public static final int THEME_LIGHT = 0;
|
||||
public static final int THEME_DARK = 1;
|
||||
|
||||
private static Typeface fontAwesome;
|
||||
private static Integer fixedTheme;
|
||||
|
||||
public static void setFixedTheme(Integer fixedTheme)
|
||||
{
|
||||
UIHelper.fixedTheme = fixedTheme;
|
||||
}
|
||||
|
||||
public interface OnSavedListener
|
||||
{
|
||||
void onSaved(Command command, Object savedObject);
|
||||
}
|
||||
|
||||
public static Typeface getFontAwesome(Context context)
|
||||
{
|
||||
if(fontAwesome == null)
|
||||
fontAwesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
||||
|
||||
return fontAwesome;
|
||||
}
|
||||
|
||||
public static void showSoftKeyboard(View view)
|
||||
{
|
||||
InputMethodManager imm = (InputMethodManager) view.getContext()
|
||||
@@ -87,6 +108,14 @@ public abstract class UIHelper
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
public static Integer getColorAttribute(Context context, AttributeSet attrs, String name,
|
||||
Integer defaultValue)
|
||||
{
|
||||
int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0);
|
||||
if (resId != 0) return context.getResources().getColor(resId);
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
public static int getIntAttribute(Context context, AttributeSet attrs, String name,
|
||||
int defaultValue)
|
||||
{
|
||||
@@ -95,6 +124,14 @@ public abstract class UIHelper
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
public static boolean getBooleanAttribute(Context context, AttributeSet attrs, String name,
|
||||
boolean defaultValue)
|
||||
{
|
||||
String boolText = getAttribute(context, attrs, name, null);
|
||||
if(boolText != null) return Boolean.parseBoolean(boolText);
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
public static float getFloatAttribute(Context context, AttributeSet attrs, String name,
|
||||
float defaultValue)
|
||||
{
|
||||
@@ -152,16 +189,133 @@ public abstract class UIHelper
|
||||
|
||||
public static boolean isLocaleFullyTranslated()
|
||||
{
|
||||
String fullyTranslatedLanguages[] = { "en", "ar", "cs", "de", "it", "ja", "ko", "po", "pl",
|
||||
"pt", "ru", "sv", "zh", "es" };
|
||||
// TODO: Move this to another place, or detect automatically
|
||||
String fullyTranslatedLanguages[] = { "ca", "zh", "en", "de", "in", "it", "ko", "pl", "pt",
|
||||
"es", "tk", "uk", "ja", "fr", "hr", "sl"};
|
||||
|
||||
final String currentLanguage = Locale.getDefault().getLanguage();
|
||||
|
||||
Log.d("UIHelper", String.format("lang=%s", currentLanguage));
|
||||
|
||||
for(String lang : fullyTranslatedLanguages)
|
||||
if(currentLanguage.equals(lang)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static float getScreenWidth(Context context)
|
||||
{
|
||||
return context.getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
|
||||
public static int getStyledColor(Context context, int attrId)
|
||||
{
|
||||
TypedArray ta = getTypedArray(context, attrId);
|
||||
int color = ta.getColor(0, 0);
|
||||
ta.recycle();
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private static TypedArray getTypedArray(Context context, int attrId)
|
||||
{
|
||||
int[] attrs = new int[]{ attrId };
|
||||
if(fixedTheme != null)
|
||||
return context.getTheme().obtainStyledAttributes(fixedTheme, attrs);
|
||||
else
|
||||
return context.obtainStyledAttributes(attrs);
|
||||
}
|
||||
|
||||
public static Drawable getStyledDrawable(Context context, int attrId)
|
||||
{
|
||||
TypedArray ta = getTypedArray(context, attrId);
|
||||
Drawable drawable = ta.getDrawable(0);
|
||||
ta.recycle();
|
||||
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public static boolean getStyledBoolean(Context context, int attrId)
|
||||
{
|
||||
TypedArray ta = getTypedArray(context, attrId);
|
||||
boolean bool = ta.getBoolean(0, false);
|
||||
ta.recycle();
|
||||
|
||||
return bool;
|
||||
}
|
||||
|
||||
public static float getStyledFloat(Context context, int attrId)
|
||||
{
|
||||
TypedArray ta = getTypedArray(context, attrId);
|
||||
float f = ta.getFloat(0, 0);
|
||||
ta.recycle();
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static int getStyleResource(Context context, int attrId)
|
||||
{
|
||||
TypedArray ta = getTypedArray(context, attrId);
|
||||
int resourceId = ta.getResourceId(0, -1);
|
||||
ta.recycle();
|
||||
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public static void applyCurrentTheme(Activity activity)
|
||||
{
|
||||
switch(getCurrentTheme())
|
||||
{
|
||||
case THEME_DARK:
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
boolean pureBlackEnabled = prefs.getBoolean("pref_pure_black", false);
|
||||
|
||||
if(pureBlackEnabled)
|
||||
activity.setTheme(R.style.AppBaseThemeDark_PureBlack);
|
||||
else
|
||||
activity.setTheme(R.style.AppBaseThemeDark);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case THEME_LIGHT:
|
||||
default:
|
||||
activity.setTheme(R.style.AppBaseTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCurrentTheme()
|
||||
{
|
||||
Context appContext = HabitsApplication.getContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
return prefs.getInt("pref_theme", THEME_LIGHT);
|
||||
}
|
||||
|
||||
public static void setCurrentTheme(int theme)
|
||||
{
|
||||
Context appContext = HabitsApplication.getContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
prefs.edit().putInt("pref_theme", theme).apply();
|
||||
}
|
||||
|
||||
public static boolean isNightMode()
|
||||
{
|
||||
return getCurrentTheme() == THEME_DARK;
|
||||
}
|
||||
|
||||
|
||||
public static void setDefaultScoreInterval(Context context, int position)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putInt("pref_score_view_interval", position).apply();
|
||||
}
|
||||
|
||||
public static int getDefaultScoreInterval(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1);
|
||||
if(defaultScoreInterval > 5 || defaultScoreInterval < 0) defaultScoreInterval = 1;
|
||||
|
||||
return defaultScoreInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
package org.isoron.uhabits.io;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.CheckmarkList;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -64,7 +66,10 @@ public class HabitsCSVExporter
|
||||
|
||||
for(Habit h : habits)
|
||||
{
|
||||
String habitDirName = String.format("%03d %s/", h.position + 1, h.name);
|
||||
String sane = sanitizeFilename(h.name);
|
||||
String habitDirName = String.format("%03d %s", h.position + 1, sane);
|
||||
habitDirName = habitDirName.trim() + "/";
|
||||
|
||||
new File(exportDirName + habitDirName).mkdirs();
|
||||
generateDirs.add(habitDirName);
|
||||
|
||||
@@ -73,6 +78,13 @@ public class HabitsCSVExporter
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String sanitizeFilename(String name)
|
||||
{
|
||||
String s = name.replaceAll("[^a-zA-Z0-9\\._-]+", "");
|
||||
return s.substring(0, Math.min(s.length(), 100));
|
||||
}
|
||||
|
||||
private void writeScores(String habitDirName, ScoreList scores) throws IOException
|
||||
{
|
||||
String path = habitDirName + "Scores.csv";
|
||||
|
||||
@@ -71,7 +71,11 @@ public class Habit extends Model
|
||||
public Integer freqDen;
|
||||
|
||||
/**
|
||||
* Color of the habit. The format is the same as android.graphics.Color.
|
||||
* Color of the habit.
|
||||
*
|
||||
* This number is not an android.graphics.Color, but an index to the activity color palette,
|
||||
* which changes according to the theme. To convert this color into an android.graphics.Color,
|
||||
* use ColorHelper.getColor(context, habit.color).
|
||||
*/
|
||||
@Column(name = "color")
|
||||
public Integer color;
|
||||
@@ -166,7 +170,7 @@ public class Habit extends Model
|
||||
*/
|
||||
public Habit()
|
||||
{
|
||||
this.color = ColorHelper.palette[5];
|
||||
this.color = 5;
|
||||
this.position = Habit.countWithArchived();
|
||||
this.highlight = 0;
|
||||
this.archived = 0;
|
||||
@@ -485,15 +489,23 @@ public class Habit extends Model
|
||||
*/
|
||||
public static void writeCSV(List<Habit> habits, Writer out) throws IOException
|
||||
{
|
||||
String header[] = { "Name", "Description", "NumRepetitions", "Interval", "Color" };
|
||||
String header[] = { "Position", "Name", "Description", "NumRepetitions", "Interval", "Color" };
|
||||
|
||||
CSVWriter csv = new CSVWriter(out);
|
||||
csv.writeNext(header, false);
|
||||
|
||||
for(Habit habit : habits)
|
||||
{
|
||||
String[] cols = { habit.name, habit.description, Integer.toString(habit.freqNum),
|
||||
Integer.toString(habit.freqDen), ColorHelper.toHTML(habit.color) };
|
||||
String[] cols =
|
||||
{
|
||||
String.format("%03d", habit.position + 1),
|
||||
habit.name,
|
||||
habit.description,
|
||||
Integer.toString(habit.freqNum),
|
||||
Integer.toString(habit.freqDen),
|
||||
ColorHelper.toHTML(ColorHelper.CSV_PALETTE[habit.color])
|
||||
};
|
||||
|
||||
csv.writeNext(cols, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* 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.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class CheckmarkView extends View implements HabitDataView
|
||||
{
|
||||
private Paint pCard;
|
||||
private Paint pIcon;
|
||||
|
||||
private int primaryColor;
|
||||
private int timesColor;
|
||||
private int darkGrey;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private float leftMargin;
|
||||
private float topMargin;
|
||||
private float padding;
|
||||
private String label;
|
||||
|
||||
private String fa_check;
|
||||
private String fa_times;
|
||||
|
||||
private int check_status;
|
||||
|
||||
private Rect rect;
|
||||
private TextPaint textPaint;
|
||||
private StaticLayout labelLayout;
|
||||
private Habit habit;
|
||||
|
||||
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);
|
||||
|
||||
primaryColor = ColorHelper.palette[10];
|
||||
timesColor = Color.argb(128, 255, 255, 255);
|
||||
darkGrey = Color.argb(64, 0, 0, 0);
|
||||
|
||||
rect = new Rect();
|
||||
check_status = 0;
|
||||
label = "Habit";
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
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 = (width * 0.015f);
|
||||
topMargin = (height * 0.015f);
|
||||
padding = 8 * leftMargin;
|
||||
textPaint.setTextSize(0.15f * width);
|
||||
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
public void refreshData()
|
||||
{
|
||||
this.check_status = habit.checkmarks.getTodayValue();
|
||||
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color),
|
||||
Color.blue(habit.color));
|
||||
this.label = habit.name;
|
||||
|
||||
updateLabel();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void updateLabel()
|
||||
{
|
||||
textPaint.setColor(Color.WHITE);
|
||||
labelLayout = new StaticLayout(label, textPaint,
|
||||
(int) (width - 2 * leftMargin - 2 * padding),
|
||||
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataView
|
||||
{
|
||||
private int activeColor;
|
||||
private float percentage;
|
||||
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
private RingView ring;
|
||||
private TextView label;
|
||||
private int checkmarkValue;
|
||||
|
||||
public CheckmarkWidgetView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckmarkWidgetView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
ring = (RingView) findViewById(R.id.scoreRing);
|
||||
label = (TextView) findViewById(R.id.label);
|
||||
|
||||
if(ring != null) ring.setIsTransparencyEnabled(true);
|
||||
|
||||
if(isInEditMode())
|
||||
{
|
||||
percentage = 0.75f;
|
||||
name = "Wake up early";
|
||||
activeColor = ColorHelper.CSV_PALETTE[6];
|
||||
checkmarkValue = Checkmark.CHECKED_EXPLICITLY;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
super.setHabit(habit);
|
||||
this.name = habit.name;
|
||||
this.activeColor = ColorHelper.getColor(getContext(), habit.color);
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh()
|
||||
{
|
||||
if (backgroundPaint == null || frame == null || ring == null) return;
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
String text;
|
||||
int backgroundColor;
|
||||
int foregroundColor;
|
||||
|
||||
switch (checkmarkValue)
|
||||
{
|
||||
case Checkmark.CHECKED_EXPLICITLY:
|
||||
text = getResources().getString(R.string.fa_check);
|
||||
backgroundColor = activeColor;
|
||||
foregroundColor =
|
||||
UIHelper.getStyledColor(context, R.attr.highContrastReverseTextColor);
|
||||
|
||||
setShadowAlpha(0x4f);
|
||||
rebuildBackground();
|
||||
|
||||
backgroundPaint.setColor(backgroundColor);
|
||||
frame.setBackgroundDrawable(background);
|
||||
break;
|
||||
|
||||
case Checkmark.CHECKED_IMPLICITLY:
|
||||
text = getResources().getString(R.string.fa_check);
|
||||
backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor);
|
||||
foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
|
||||
break;
|
||||
|
||||
case Checkmark.UNCHECKED:
|
||||
default:
|
||||
text = getResources().getString(R.string.fa_times);
|
||||
backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor);
|
||||
foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor);
|
||||
break;
|
||||
}
|
||||
|
||||
ring.setPercentage(percentage);
|
||||
ring.setColor(foregroundColor);
|
||||
ring.setBackgroundColor(backgroundColor);
|
||||
ring.setText(text);
|
||||
|
||||
label.setText(name);
|
||||
label.setTextColor(foregroundColor);
|
||||
|
||||
requestLayout();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
float w = width;
|
||||
float h = width * 1.25f;
|
||||
float scale = Math.min(width / w, height / h);
|
||||
|
||||
w *= scale;
|
||||
h *= scale;
|
||||
|
||||
if(h < getResources().getDimension(R.dimen.checkmarkWidget_heightBreakpoint))
|
||||
ring.setVisibility(GONE);
|
||||
else
|
||||
ring.setVisibility(VISIBLE);
|
||||
|
||||
widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY);
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY);
|
||||
|
||||
float textSize = 0.15f * h;
|
||||
float maxTextSize = getResources().getDimension(R.dimen.smallerTextSize);
|
||||
textSize = Math.min(textSize, maxTextSize);
|
||||
|
||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
ring.setTextSize(textSize);
|
||||
ring.setThickness(0.15f * textSize);
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshData()
|
||||
{
|
||||
if(habit == null) return;
|
||||
this.percentage = (float) habit.scores.getTodayValue() / Score.MAX_VALUE;
|
||||
this.checkmarkValue = habit.checkmarks.getTodayValue();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Integer getInnerLayoutId()
|
||||
{
|
||||
return R.layout.widget_checkmark;
|
||||
}
|
||||
}
|
||||
@@ -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.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class GraphWidgetView extends HabitWidgetView implements HabitDataView
|
||||
{
|
||||
|
||||
private final HabitDataView dataView;
|
||||
private TextView title;
|
||||
|
||||
public GraphWidgetView(Context context, HabitDataView dataView)
|
||||
{
|
||||
super(context);
|
||||
this.dataView = dataView;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
ViewGroup.LayoutParams params =
|
||||
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
((View) dataView).setLayoutParams(params);
|
||||
|
||||
ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame);
|
||||
innerFrame.addView(((View) dataView));
|
||||
|
||||
title = (TextView) findViewById(R.id.title);
|
||||
title.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
super.setHabit(habit);
|
||||
dataView.setHabit(habit);
|
||||
title.setText(habit.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshData()
|
||||
{
|
||||
if(habit == null) return;
|
||||
dataView.refreshData();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Integer getInnerLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,14 @@ 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.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -56,13 +57,12 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
private int nColumns;
|
||||
|
||||
private int textColor;
|
||||
private int dimmedTextColor;
|
||||
private int gridColor;
|
||||
private int[] colors;
|
||||
private int primaryColor;
|
||||
private boolean isBackgroundTransparent;
|
||||
|
||||
private HashMap<Long, Integer[]> frequency;
|
||||
private String wdays[];
|
||||
|
||||
public HabitFrequencyView(Context context)
|
||||
{
|
||||
@@ -73,7 +73,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
public HabitFrequencyView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), 7);
|
||||
this.frequency = new HashMap<>();
|
||||
init();
|
||||
}
|
||||
@@ -89,8 +89,6 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
wdays = DateHelper.getShortDayNames();
|
||||
|
||||
dfMonth = DateHelper.getDateFormat("MMM");
|
||||
dfYear = DateHelper.getDateFormat("yyyy");
|
||||
|
||||
@@ -101,25 +99,15 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if (isBackgroundTransparent)
|
||||
{
|
||||
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
|
||||
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
||||
gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
|
||||
|
||||
colors = new int[4];
|
||||
|
||||
colors[0] = Color.rgb(230, 230, 230);
|
||||
colors[0] = gridColor;
|
||||
colors[3] = primaryColor;
|
||||
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
|
||||
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
|
||||
@@ -247,11 +235,13 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
float rowHeight = rect.height() / 8.0f;
|
||||
prevRect.set(rect);
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
Integer[] localeWeekdayList = DateHelper.getLocaleWeekdayList();
|
||||
for (int j = 0; j < localeWeekdayList.length; j++)
|
||||
{
|
||||
rect.set(0, 0, baseSize, baseSize);
|
||||
rect.offset(prevRect.left, prevRect.top + baseSize * i);
|
||||
rect.offset(prevRect.left, prevRect.top + baseSize * j);
|
||||
|
||||
int i = DateHelper.javaWeekdayToLoopWeekday(localeWeekdayList[j]);
|
||||
if(values != null)
|
||||
drawMarker(canvas, rect, values[i]);
|
||||
|
||||
@@ -287,11 +277,10 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV
|
||||
|
||||
pText.setTextAlign(Paint.Align.LEFT);
|
||||
pText.setColor(textColor);
|
||||
pGrid.setColor(dimmedTextColor);
|
||||
pGrid.setColor(gridColor);
|
||||
|
||||
for (int i = 0; i < nRows; i++)
|
||||
{
|
||||
canvas.drawText(wdays[i], rGrid.right - columnWidth,
|
||||
for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT)) {
|
||||
canvas.drawText(day, rGrid.right - columnWidth,
|
||||
rGrid.top + rowHeight / 2 + 0.25f * em, pText);
|
||||
|
||||
pGrid.setStrokeWidth(1f);
|
||||
|
||||
@@ -57,19 +57,20 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
private float columnHeight;
|
||||
private int nColumns;
|
||||
|
||||
private String wdays[];
|
||||
private SimpleDateFormat dfMonth;
|
||||
private SimpleDateFormat dfYear;
|
||||
|
||||
private Calendar baseDate;
|
||||
private int nDays;
|
||||
private int todayWeekday;
|
||||
/** 0-based-position of today in the column */
|
||||
private int todayPositionInColumn;
|
||||
private int colors[];
|
||||
private RectF baseLocation;
|
||||
private int primaryColor;
|
||||
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private int reverseTextColor;
|
||||
private boolean isEditable;
|
||||
|
||||
public HabitHistoryView(Context context)
|
||||
@@ -92,13 +93,12 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
|
||||
private void init()
|
||||
{
|
||||
createPaints();
|
||||
createColors();
|
||||
createPaints();
|
||||
|
||||
isEditable = false;
|
||||
checkmarks = new int[0];
|
||||
primaryColor = ColorHelper.palette[7];
|
||||
wdays = DateHelper.getShortDayNames();
|
||||
primaryColor = ColorHelper.getColor(getContext(), 7);
|
||||
dfMonth = DateHelper.getDateFormat("MMM");
|
||||
dfYear = DateHelper.getDateFormat("yyyy");
|
||||
|
||||
@@ -111,10 +111,11 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
|
||||
|
||||
nDays = (nColumns - 1) * 7;
|
||||
todayWeekday = DateHelper.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK) % 7;
|
||||
int realWeekday = DateHelper.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK);
|
||||
todayPositionInColumn = (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7;
|
||||
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,8 +134,9 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
setScrollerBucketSize((int) baseSize);
|
||||
|
||||
squareSpacing = UIHelper.dpToPixels(getContext(), 1.0f);
|
||||
float maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
|
||||
float textSize = Math.min(baseSize * 0.5f, maxTextSize);
|
||||
float maxTextSize = getResources().getDimension(R.dimen.regularTextSize);
|
||||
float textSize = height * 0.06f;
|
||||
textSize = Math.min(textSize, maxTextSize);
|
||||
|
||||
pSquareFg.setTextSize(textSize);
|
||||
pTextHeader.setTextSize(textSize);
|
||||
@@ -155,7 +157,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
{
|
||||
float width = 0;
|
||||
|
||||
for(String w : wdays)
|
||||
for(String w : DateHelper.getLocaleDayNames(Calendar.SHORT))
|
||||
width = Math.max(width, pSquareFg.measureText(w));
|
||||
|
||||
return width;
|
||||
@@ -164,7 +166,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f);
|
||||
@@ -179,15 +181,17 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
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);
|
||||
textColor = Color.WHITE;
|
||||
reverseTextColor = Color.WHITE;
|
||||
}
|
||||
else
|
||||
{
|
||||
colors = new int[3];
|
||||
colors[0] = Color.argb(25, 0, 0, 0);
|
||||
colors[0] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
|
||||
colors[1] = Color.argb(127, red, green, blue);
|
||||
colors[2] = primaryColor;
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
||||
reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,10 +202,8 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
pTextHeader.setAntiAlias(true);
|
||||
|
||||
pSquareBg = new Paint();
|
||||
pSquareBg.setColor(primaryColor);
|
||||
|
||||
pSquareFg = new Paint();
|
||||
pSquareFg.setColor(Color.WHITE);
|
||||
pSquareFg.setAntiAlias(true);
|
||||
pSquareFg.setTextAlign(Align.CENTER);
|
||||
}
|
||||
@@ -274,9 +276,10 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
|
||||
for (int j = 0; j < 7; j++)
|
||||
{
|
||||
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayWeekday))
|
||||
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayPositionInColumn))
|
||||
{
|
||||
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
|
||||
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) +
|
||||
todayPositionInColumn - j;
|
||||
drawSquare(canvas, location, date, checkmarkOffset);
|
||||
}
|
||||
|
||||
@@ -291,6 +294,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
||||
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
|
||||
|
||||
pSquareFg.setColor(reverseTextColor);
|
||||
canvas.drawRect(location, pSquareBg);
|
||||
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
|
||||
canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg);
|
||||
@@ -298,11 +302,13 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
||||
|
||||
private void drawAxis(Canvas canvas, RectF location)
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
float verticalOffset = pTextHeader.getFontSpacing() * 0.4f;
|
||||
|
||||
for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT))
|
||||
{
|
||||
location.offset(0, columnWidth);
|
||||
canvas.drawText(wdays[i], location.left + headerTextOffset,
|
||||
location.bottom - headerTextOffset, pTextHeader);
|
||||
canvas.drawText(day, location.left + headerTextOffset,
|
||||
location.centerY() + verticalOffset, pTextHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
@@ -32,6 +33,7 @@ import android.util.AttributeSet;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
@@ -47,6 +49,8 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
public static final PorterDuffXfermode XFERMODE_SRC =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
||||
|
||||
public static int DEFAULT_BUCKET_SIZES[] = { 1, 7, 31, 92, 365 };
|
||||
|
||||
private Paint pGrid;
|
||||
private float em;
|
||||
private Habit habit;
|
||||
@@ -65,15 +69,19 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
private int nColumns;
|
||||
|
||||
private int textColor;
|
||||
private int dimmedTextColor;
|
||||
private int gridColor;
|
||||
|
||||
@Nullable
|
||||
private int[] scores;
|
||||
|
||||
private int primaryColor;
|
||||
private boolean isBackgroundTransparent;
|
||||
private int bucketSize = 7;
|
||||
private int footerHeight;
|
||||
private int backgroundColor;
|
||||
|
||||
private Bitmap drawingCache;
|
||||
private Canvas cacheCanvas;
|
||||
private boolean isTransparencyEnabled;
|
||||
|
||||
public HabitScoreView(Context context)
|
||||
{
|
||||
@@ -84,7 +92,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
public HabitScoreView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), 7);
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -110,21 +118,11 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), 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);
|
||||
}
|
||||
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
||||
gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
|
||||
backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor);
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
@@ -153,8 +151,9 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
{
|
||||
if(height < 9) height = 200;
|
||||
|
||||
int maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
|
||||
pText.setTextSize(Math.min(height * 0.047f, maxTextSize));
|
||||
float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize);
|
||||
float textSize = height * 0.06f;
|
||||
pText.setTextSize(Math.min(textSize, maxTextSize));
|
||||
em = pText.getFontSpacing();
|
||||
|
||||
footerHeight = (int)(3 * em);
|
||||
@@ -167,12 +166,25 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
|
||||
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
|
||||
|
||||
columnHeight = 8 * baseSize;
|
||||
nColumns = (int) (width / columnWidth);
|
||||
columnWidth = (float) width / nColumns;
|
||||
|
||||
columnHeight = 8 * baseSize;
|
||||
|
||||
float minStrokeWidth = UIHelper.dpToPixels(getContext(), 1);
|
||||
pGraph.setTextSize(baseSize * 0.5f);
|
||||
pGraph.setStrokeWidth(baseSize * 0.1f);
|
||||
pGrid.setStrokeWidth(baseSize * 0.05f);
|
||||
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
|
||||
|
||||
if(isTransparencyEnabled)
|
||||
initCache(width, height);
|
||||
}
|
||||
|
||||
private void initCache(int width, int height)
|
||||
{
|
||||
if (drawingCache != null) drawingCache.recycle();
|
||||
drawingCache = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
cacheCanvas = new Canvas(drawingCache);
|
||||
}
|
||||
|
||||
public void refreshData()
|
||||
@@ -211,12 +223,26 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
Canvas activeCanvas;
|
||||
|
||||
if(isTransparencyEnabled)
|
||||
{
|
||||
if(drawingCache == null) initCache(getWidth(), getHeight());
|
||||
|
||||
activeCanvas = cacheCanvas;
|
||||
drawingCache.eraseColor(Color.TRANSPARENT);
|
||||
}
|
||||
else
|
||||
{
|
||||
activeCanvas = canvas;
|
||||
}
|
||||
|
||||
if (habit == null || scores == null) return;
|
||||
|
||||
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
||||
rect.offset(0, paddingTop);
|
||||
|
||||
drawGrid(canvas, rect);
|
||||
drawGrid(activeCanvas, rect);
|
||||
|
||||
pText.setColor(textColor);
|
||||
pGraph.setColor(primaryColor);
|
||||
@@ -241,24 +267,28 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
int height = (int) (columnHeight * relativeScore);
|
||||
|
||||
rect.set(0, 0, baseSize, baseSize);
|
||||
rect.offset(k * columnWidth, paddingTop + columnHeight - height - columnWidth / 2);
|
||||
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
||||
paddingTop + columnHeight - height - baseSize / 2);
|
||||
|
||||
if (!prevRect.isEmpty())
|
||||
{
|
||||
drawLine(canvas, prevRect, rect);
|
||||
drawMarker(canvas, prevRect);
|
||||
drawLine(activeCanvas, prevRect, rect);
|
||||
drawMarker(activeCanvas, prevRect);
|
||||
}
|
||||
|
||||
if (k == nColumns - 1) drawMarker(canvas, rect);
|
||||
if (k == nColumns - 1) drawMarker(activeCanvas, rect);
|
||||
|
||||
prevRect.set(rect);
|
||||
rect.set(0, 0, columnWidth, columnHeight);
|
||||
rect.offset(k * columnWidth, paddingTop);
|
||||
|
||||
drawFooter(canvas, rect, currentDate);
|
||||
drawFooter(activeCanvas, rect, currentDate);
|
||||
|
||||
currentDate += bucketSize * DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
if(activeCanvas != canvas)
|
||||
canvas.drawBitmap(drawingCache, 0, 0, null);
|
||||
}
|
||||
|
||||
private int skipYear = 0;
|
||||
@@ -322,7 +352,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
|
||||
pText.setTextAlign(Paint.Align.LEFT);
|
||||
pText.setColor(textColor);
|
||||
pGrid.setColor(dimmedTextColor);
|
||||
pGrid.setColor(gridColor);
|
||||
|
||||
for (int i = 0; i < nRows; i++)
|
||||
{
|
||||
@@ -345,7 +375,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
private void drawMarker(Canvas canvas, RectF rect)
|
||||
{
|
||||
rect.inset(baseSize * 0.15f, baseSize * 0.15f);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(baseSize * 0.1f, baseSize * 0.1f);
|
||||
@@ -353,22 +383,23 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(baseSize * 0.1f, baseSize * 0.1f);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
if(isTransparencyEnabled)
|
||||
pGraph.setXfermode(XFERMODE_SRC);
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
public void setIsTransparencyEnabled(boolean enabled)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
this.isTransparencyEnabled = enabled;
|
||||
createColors();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
||||
{
|
||||
if(isBackgroundTransparent)
|
||||
if(isTransparencyEnabled)
|
||||
p.setXfermode(mode);
|
||||
else
|
||||
p.setColor(color);
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Streak;
|
||||
|
||||
@@ -53,7 +54,6 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
private List<Streak> streaks;
|
||||
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private DateFormat dateFormat;
|
||||
private int width;
|
||||
private float em;
|
||||
@@ -61,6 +61,8 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
private float textMargin;
|
||||
private boolean shouldShowLabels;
|
||||
private int maxStreakCount;
|
||||
private int textColor;
|
||||
private int reverseTextColor;
|
||||
|
||||
public HabitStreakView(Context context)
|
||||
{
|
||||
@@ -71,7 +73,7 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
public HabitStreakView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), 7);
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -109,10 +111,11 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
maxStreakCount = height / baseSize;
|
||||
this.width = width;
|
||||
|
||||
int maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
|
||||
float regularTextSize = Math.min(baseSize * 0.56f, maxTextSize);
|
||||
float minTextSize = getResources().getDimension(R.dimen.tinyTextSize);
|
||||
float maxTextSize = getResources().getDimension(R.dimen.regularTextSize);
|
||||
float textSize = baseSize * 0.5f;
|
||||
|
||||
paint.setTextSize(regularTextSize);
|
||||
paint.setTextSize(Math.max(Math.min(textSize, maxTextSize), minTextSize));
|
||||
em = paint.getFontSpacing();
|
||||
textMargin = 0.5f * em;
|
||||
|
||||
@@ -122,36 +125,19 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
|
||||
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
|
||||
}
|
||||
this.primaryColor = ColorHelper.getColor(getContext(), habit.color);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor);
|
||||
textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
||||
reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor);
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
@@ -216,7 +202,7 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
if(shouldShowLabels) availableWidth -= 2 * textMargin;
|
||||
|
||||
float barWidth = percentage * availableWidth;
|
||||
float minBarWidth = paint.measureText(streak.length.toString());
|
||||
float minBarWidth = paint.measureText(streak.length.toString()) + em;
|
||||
barWidth = Math.max(barWidth, minBarWidth);
|
||||
|
||||
float gap = (width - barWidth) / 2;
|
||||
@@ -229,7 +215,7 @@ public class HabitStreakView extends View implements HabitDataView
|
||||
|
||||
float yOffset = rect.centerY() + 0.3f * em;
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setColor(reverseTextColor);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
canvas.drawText(streak.length.toString(), rect.centerX(), yOffset, paint);
|
||||
|
||||
|
||||
117
app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class HabitWidgetView extends FrameLayout implements HabitDataView
|
||||
{
|
||||
@Nullable
|
||||
protected InsetDrawable background;
|
||||
|
||||
@Nullable
|
||||
protected Paint backgroundPaint;
|
||||
|
||||
@Nullable
|
||||
protected Habit habit;
|
||||
protected ViewGroup frame;
|
||||
|
||||
public void setShadowAlpha(int shadowAlpha)
|
||||
{
|
||||
this.shadowAlpha = shadowAlpha;
|
||||
}
|
||||
|
||||
private int shadowAlpha;
|
||||
|
||||
public HabitWidgetView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public HabitWidgetView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
inflate(getContext(), getInnerLayoutId(), this);
|
||||
shadowAlpha = (int) (255 * UIHelper.getStyledFloat(getContext(), R.attr.widgetShadowAlpha));
|
||||
rebuildBackground();
|
||||
}
|
||||
|
||||
protected abstract @NonNull Integer getInnerLayoutId();
|
||||
|
||||
protected void rebuildBackground()
|
||||
{
|
||||
Context context = getContext();
|
||||
|
||||
int backgroundAlpha =
|
||||
(int) (255 * UIHelper.getStyledFloat(context, R.attr.widgetBackgroundAlpha));
|
||||
|
||||
int shadowRadius = (int) UIHelper.dpToPixels(context, 2);
|
||||
int shadowOffset = (int) UIHelper.dpToPixels(context, 1);
|
||||
int shadowColor = Color.argb(shadowAlpha, 0, 0, 0);
|
||||
|
||||
float cornerRadius = UIHelper.dpToPixels(context, 5);
|
||||
float[] radii = new float[8];
|
||||
Arrays.fill(radii, cornerRadius);
|
||||
|
||||
RoundRectShape shape = new RoundRectShape(radii, null, null);
|
||||
ShapeDrawable innerDrawable = new ShapeDrawable(shape);
|
||||
|
||||
int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0);
|
||||
int insetRightBottom = shadowRadius + shadowOffset;
|
||||
|
||||
background = new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, insetRightBottom,
|
||||
insetRightBottom);
|
||||
backgroundPaint = innerDrawable.getPaint();
|
||||
backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor);
|
||||
backgroundPaint.setColor(UIHelper.getStyledColor(context, R.attr.cardBackgroundColor));
|
||||
backgroundPaint.setAlpha(backgroundAlpha);
|
||||
|
||||
frame = (ViewGroup) findViewById(R.id.frame);
|
||||
if(frame != null) frame.setBackgroundDrawable(background);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHabit(@NonNull Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class NumberView extends View
|
||||
this.textSize = UIHelper.getFloatAttribute(context, attrs, "textSize",
|
||||
getResources().getDimension(R.dimen.regularTextSize));
|
||||
|
||||
this.color = ColorHelper.palette[7];
|
||||
this.color = ColorHelper.getColor(getContext(), 7);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -79,6 +80,6 @@ public class RepetitionCountView extends NumberView implements HabitDataView
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
setColor(habit.color);
|
||||
setColor(ColorHelper.getColor(getContext(), habit.color));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,42 +19,62 @@
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
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.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
public class RingView extends View
|
||||
{
|
||||
public static final PorterDuffXfermode XFERMODE_CLEAR =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
||||
|
||||
private int color;
|
||||
private float precision;
|
||||
private float percentage;
|
||||
private float labelMarginTop;
|
||||
private TextPaint pRing;
|
||||
private String label;
|
||||
private int diameter;
|
||||
private float thickness;
|
||||
|
||||
private RectF rect;
|
||||
private StaticLayout labelLayout;
|
||||
private TextPaint pRing;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private float diameter;
|
||||
private Integer backgroundColor;
|
||||
private Integer inactiveColor;
|
||||
|
||||
private float maxDiameter;
|
||||
private float em;
|
||||
private String text;
|
||||
private float textSize;
|
||||
private int fadedTextColor;
|
||||
private boolean enableFontAwesome;
|
||||
|
||||
@Nullable
|
||||
private Bitmap drawingCache;
|
||||
private Canvas cacheCanvas;
|
||||
|
||||
private boolean isTransparencyEnabled;
|
||||
|
||||
public RingView(Context context)
|
||||
{
|
||||
super(context);
|
||||
|
||||
percentage = 0.0f;
|
||||
precision = 0.01f;
|
||||
color = ColorHelper.CSV_PALETTE[0];
|
||||
thickness = UIHelper.dpToPixels(getContext(), 2);
|
||||
text = "";
|
||||
textSize = context.getResources().getDimension(R.dimen.smallTextSize);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -62,9 +82,24 @@ public class RingView extends View
|
||||
{
|
||||
super(context, attrs);
|
||||
|
||||
this.label = UIHelper.getAttribute(context, attrs, "label", "Label");
|
||||
this.maxDiameter = UIHelper.getFloatAttribute(context, attrs, "maxDiameter", 100);
|
||||
this.maxDiameter = UIHelper.dpToPixels(context, maxDiameter);
|
||||
percentage = UIHelper.getFloatAttribute(context, attrs, "percentage", 0);
|
||||
precision = UIHelper.getFloatAttribute(context, attrs, "precision", 0.01f);
|
||||
|
||||
color = UIHelper.getColorAttribute(context, attrs, "color", 0);
|
||||
backgroundColor = UIHelper.getColorAttribute(context, attrs, "backgroundColor", null);
|
||||
inactiveColor = UIHelper.getColorAttribute(context, attrs, "inactiveColor", null);
|
||||
|
||||
thickness = UIHelper.getFloatAttribute(context, attrs, "thickness", 0);
|
||||
thickness = UIHelper.dpToPixels(context, thickness);
|
||||
|
||||
float defaultTextSize = context.getResources().getDimension(R.dimen.smallTextSize);
|
||||
textSize = UIHelper.getFloatAttribute(context, attrs, "textSize", defaultTextSize);
|
||||
textSize = UIHelper.spToPixels(context, textSize);
|
||||
|
||||
text = UIHelper.getAttribute(context, attrs, "text", "");
|
||||
|
||||
enableFontAwesome = UIHelper.getBooleanAttribute(context, attrs, "enableFontAwesome", false);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -74,14 +109,16 @@ public class RingView extends View
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setMaxDiameter(float maxDiameter)
|
||||
public void setTextSize(float textSize)
|
||||
{
|
||||
this.maxDiameter = maxDiameter;
|
||||
this.textSize = textSize;
|
||||
}
|
||||
|
||||
public void setLabel(String label)
|
||||
@Override
|
||||
public void setBackgroundColor(int backgroundColor)
|
||||
{
|
||||
this.label = label;
|
||||
this.backgroundColor = backgroundColor;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setPercentage(float percentage)
|
||||
@@ -90,6 +127,24 @@ public class RingView extends View
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setPrecision(float precision)
|
||||
{
|
||||
this.precision = precision;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setThickness(float thickness)
|
||||
{
|
||||
this.thickness = thickness;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
pRing = new TextPaint();
|
||||
@@ -97,59 +152,97 @@ public class RingView extends View
|
||||
pRing.setColor(color);
|
||||
pRing.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
fadedTextColor = getResources().getColor(R.color.fadedTextColor);
|
||||
textSize = getResources().getDimension(R.dimen.smallTextSize);
|
||||
if(backgroundColor == null)
|
||||
backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor);
|
||||
|
||||
if(inactiveColor == null)
|
||||
inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastTextColor);
|
||||
|
||||
inactiveColor = ColorHelper.setAlpha(inactiveColor, 0.1f);
|
||||
|
||||
rect = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("DrawAllocation")
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
diameter = Math.min(maxDiameter, width);
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
diameter = Math.min(height, width);
|
||||
|
||||
pRing.setTextSize(textSize);
|
||||
labelMarginTop = textSize * 0.80f;
|
||||
labelLayout = new StaticLayout(label, pRing, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f,
|
||||
false);
|
||||
em = pRing.measureText("M");
|
||||
|
||||
width = Math.max(width, labelLayout.getWidth());
|
||||
height = (int) (diameter + labelLayout.getHeight() + labelMarginTop);
|
||||
setMeasuredDimension(diameter, diameter);
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh)
|
||||
{
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if(isTransparencyEnabled) reallocateCache();
|
||||
}
|
||||
|
||||
private void reallocateCache()
|
||||
{
|
||||
if (drawingCache != null) drawingCache.recycle();
|
||||
drawingCache = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
|
||||
cacheCanvas = new Canvas(drawingCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
float thickness = diameter * 0.15f;
|
||||
Canvas activeCanvas;
|
||||
|
||||
if(isTransparencyEnabled)
|
||||
{
|
||||
if(drawingCache == null) reallocateCache();
|
||||
activeCanvas = cacheCanvas;
|
||||
drawingCache.eraseColor(Color.TRANSPARENT);
|
||||
}
|
||||
else
|
||||
{
|
||||
activeCanvas = canvas;
|
||||
}
|
||||
|
||||
pRing.setColor(color);
|
||||
rect.set(0, 0, diameter, diameter);
|
||||
rect.offset((width - diameter) / 2, 0);
|
||||
canvas.drawArc(rect, -90, 360 * percentage, true, pRing);
|
||||
|
||||
pRing.setColor(Color.argb(255, 230, 230, 230));
|
||||
canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing);
|
||||
float angle = 360 * Math.round(percentage / precision) * precision;
|
||||
|
||||
pRing.setColor(Color.WHITE);
|
||||
rect.inset(thickness, thickness);
|
||||
canvas.drawArc(rect, -90, 360, true, pRing);
|
||||
activeCanvas.drawArc(rect, -90, angle, true, pRing);
|
||||
|
||||
pRing.setColor(fadedTextColor);
|
||||
pRing.setTextSize(textSize);
|
||||
float lineHeight = pRing.getFontSpacing();
|
||||
canvas.drawText(String.format("%.0f%%", percentage * 100), rect.centerX(),
|
||||
rect.centerY() + lineHeight / 3, pRing);
|
||||
pRing.setTextSize(textSize);
|
||||
canvas.translate(width / 2, diameter + labelMarginTop);
|
||||
labelLayout.draw(canvas);
|
||||
pRing.setColor(inactiveColor);
|
||||
activeCanvas.drawArc(rect, angle - 90, 360 - angle, true, pRing);
|
||||
|
||||
if(thickness > 0)
|
||||
{
|
||||
if(isTransparencyEnabled)
|
||||
pRing.setXfermode(XFERMODE_CLEAR);
|
||||
else
|
||||
pRing.setColor(backgroundColor);
|
||||
|
||||
rect.inset(thickness, thickness);
|
||||
activeCanvas.drawArc(rect, 0, 360, true, pRing);
|
||||
pRing.setXfermode(null);
|
||||
|
||||
pRing.setColor(color);
|
||||
pRing.setTextSize(textSize);
|
||||
if(enableFontAwesome) pRing.setTypeface(UIHelper.getFontAwesome(getContext()));
|
||||
activeCanvas.drawText(text, rect.centerX(), rect.centerY() + 0.4f * em, pRing);
|
||||
}
|
||||
|
||||
if(activeCanvas != canvas)
|
||||
canvas.drawBitmap(drawingCache, 0, 0, null);
|
||||
}
|
||||
|
||||
public void setIsTransparencyEnabled(boolean isTransparencyEnabled)
|
||||
{
|
||||
this.isTransparencyEnabled = isTransparencyEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
@@ -44,8 +45,11 @@ import java.io.IOException;
|
||||
|
||||
public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
{
|
||||
|
||||
private int width, height;
|
||||
private class WidgetDimensions
|
||||
{
|
||||
public int portraitWidth, portraitHeight;
|
||||
public int landscapeWidth, landscapeHeight;
|
||||
}
|
||||
|
||||
protected abstract int getDefaultHeight();
|
||||
|
||||
@@ -69,7 +73,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
|
||||
for(Integer id : appWidgetIds)
|
||||
prefs.edit().remove(getHabitIdKey(id));
|
||||
prefs.edit().remove(getHabitIdKey(id)).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,7 +100,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
private void updateWidget(Context context, AppWidgetManager manager,
|
||||
int widgetId, Bundle options)
|
||||
{
|
||||
updateWidgetSize(context, options);
|
||||
WidgetDimensions dim = getWidgetDimensions(context, options);
|
||||
|
||||
Context appContext = context.getApplicationContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
@@ -107,26 +111,34 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
Habit habit = Habit.get(habitId);
|
||||
if(habit == null)
|
||||
{
|
||||
RemoteViews errorView = new RemoteViews(context.getPackageName(),
|
||||
R.layout.widget_error);
|
||||
manager.updateAppWidget(widgetId, errorView);
|
||||
drawErrorWidget(context, manager, widgetId);
|
||||
return;
|
||||
}
|
||||
|
||||
new RenderWidgetTask(widgetId, context, habit, manager).execute();
|
||||
new RenderWidgetTask(widgetId, context, habit, dim, manager).execute();
|
||||
}
|
||||
|
||||
private void drawErrorWidget(Context context, AppWidgetManager manager, int widgetId)
|
||||
{
|
||||
RemoteViews errorView = new RemoteViews(context.getPackageName(), R.layout.widget_error);
|
||||
manager.updateAppWidget(widgetId, errorView);
|
||||
}
|
||||
|
||||
protected abstract void refreshCustomViewData(View widgetView);
|
||||
|
||||
private void savePreview(Context context, int widgetId, Bitmap widgetCache)
|
||||
private void savePreview(Context context, int widgetId, Bitmap widgetCache, int width,
|
||||
int height, String label)
|
||||
{
|
||||
try
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View view = inflater.inflate(getLayoutId(), null);
|
||||
|
||||
TextView tvLabel = (TextView) view.findViewById(R.id.label);
|
||||
if(tvLabel != null) tvLabel.setText(label);
|
||||
|
||||
ImageView iv = (ImageView) view.findViewById(R.id.imageView);
|
||||
iv.setImageBitmap(widgetCache);
|
||||
if(iv != null) iv.setImageBitmap(widgetCache);
|
||||
|
||||
view.measure(width, height);
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
@@ -134,7 +146,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
view.buildDrawingCache();
|
||||
Bitmap previewCache = view.getDrawingCache();
|
||||
|
||||
String filename = String.format("%s/%d.png", context.getExternalCacheDir(), widgetId);
|
||||
String filename = String.format("%s/%d_%d.png", context.getExternalCacheDir(), widgetId, width);
|
||||
Log.d("BaseWidgetProvider", String.format("Writing %s", filename));
|
||||
FileOutputStream out = new FileOutputStream(filename);
|
||||
|
||||
@@ -149,7 +161,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidgetSize(Context context, Bundle options)
|
||||
private WidgetDimensions getWidgetDimensions(Context context, Bundle options)
|
||||
{
|
||||
int maxWidth = getDefaultWidth();
|
||||
int minWidth = getDefaultWidth();
|
||||
@@ -168,8 +180,12 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
|
||||
}
|
||||
|
||||
width = maxWidth;
|
||||
height = maxHeight;
|
||||
WidgetDimensions ws = new WidgetDimensions();
|
||||
ws.portraitWidth = minWidth;
|
||||
ws.portraitHeight = maxHeight;
|
||||
ws.landscapeWidth = maxWidth;
|
||||
ws.landscapeHeight = minHeight;
|
||||
return ws;
|
||||
}
|
||||
|
||||
private void measureCustomView(Context context, int w, int h, View customView)
|
||||
@@ -199,37 +215,74 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
private final Context context;
|
||||
private final Habit habit;
|
||||
private final AppWidgetManager manager;
|
||||
public RemoteViews remoteViews;
|
||||
public View widgetView;
|
||||
private RemoteViews portraitRemoteViews, landscapeRemoteViews;
|
||||
private View portraitWidgetView, landscapeWidgetView;
|
||||
private WidgetDimensions dim;
|
||||
|
||||
public RenderWidgetTask(int widgetId, Context context, Habit habit,
|
||||
public RenderWidgetTask(int widgetId, Context context, Habit habit, WidgetDimensions ws,
|
||||
AppWidgetManager manager)
|
||||
{
|
||||
this.widgetId = widgetId;
|
||||
this.context = context;
|
||||
this.habit = habit;
|
||||
this.manager = manager;
|
||||
this.dim = ws;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
super.onPreExecute();
|
||||
context.setTheme(R.style.TransparentWidgetTheme);
|
||||
|
||||
remoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
|
||||
widgetView = buildCustomView(context, habit);
|
||||
measureCustomView(context, width, height, widgetView);
|
||||
manager.updateAppWidget(widgetId, remoteViews);
|
||||
portraitRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
|
||||
portraitWidgetView = buildCustomView(context, habit);
|
||||
measureCustomView(context, dim.portraitWidth, dim.portraitHeight, portraitWidgetView);
|
||||
|
||||
landscapeRemoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
|
||||
landscapeWidgetView = buildCustomView(context, habit);
|
||||
measureCustomView(context, dim.landscapeWidth, dim.landscapeHeight,
|
||||
landscapeWidgetView);
|
||||
}
|
||||
|
||||
private void updateAppWidget()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
manager.updateAppWidget(widgetId, new RemoteViews(landscapeRemoteViews,
|
||||
portraitRemoteViews));
|
||||
else
|
||||
manager.updateAppWidget(widgetId, portraitRemoteViews);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
refreshCustomViewData(widgetView);
|
||||
refreshCustomViewData(portraitWidgetView);
|
||||
refreshCustomViewData(landscapeWidgetView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
try
|
||||
{
|
||||
buildRemoteViews(portraitWidgetView, portraitRemoteViews,
|
||||
dim.portraitWidth, dim.portraitHeight);
|
||||
buildRemoteViews(landscapeWidgetView, landscapeRemoteViews,
|
||||
dim.landscapeWidth, dim.landscapeHeight);
|
||||
updateAppWidget();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
drawErrorWidget(context, manager, widgetId);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
super.onPostExecute(aVoid);
|
||||
}
|
||||
|
||||
private void buildRemoteViews(View widgetView, RemoteViews remoteViews, int width,
|
||||
int height)
|
||||
{
|
||||
widgetView.invalidate();
|
||||
widgetView.setDrawingCacheEnabled(true);
|
||||
@@ -238,14 +291,28 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
remoteViews.setTextViewText(R.id.label, habit.name);
|
||||
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
|
||||
|
||||
//savePreview(context, widgetId, drawingCache);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
{
|
||||
int imageWidth = widgetView.getMeasuredWidth();
|
||||
int imageHeight = widgetView.getMeasuredHeight();
|
||||
int p[] = getPadding(width, height, imageWidth, imageHeight);
|
||||
remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]);
|
||||
}
|
||||
|
||||
//savePreview(context, widgetId, drawingCache, width, height, habit.name);
|
||||
|
||||
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
|
||||
if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);
|
||||
|
||||
manager.updateAppWidget(widgetId, remoteViews);
|
||||
|
||||
super.onPostExecute(aVoid);
|
||||
if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.button,
|
||||
onClickIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] getPadding(int entireWidth, int entireHeight, int imageWidth,
|
||||
int imageHeight)
|
||||
{
|
||||
int w = (int) (((float) entireWidth - imageWidth) / 2);
|
||||
int h = (int) (((float) entireHeight - imageHeight) / 2);
|
||||
|
||||
return new int[]{ w, h, w, h };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ 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;
|
||||
import org.isoron.uhabits.views.CheckmarkWidgetView;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
|
||||
public class CheckmarkWidgetProvider extends BaseWidgetProvider
|
||||
@@ -33,7 +33,7 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
CheckmarkView view = new CheckmarkView(context);
|
||||
CheckmarkWidgetView view = new CheckmarkWidgetView(context);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
@@ -53,19 +53,19 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
return 125;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
return 125;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_checkmark;
|
||||
return R.layout.widget_wrapper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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.GraphWidgetView;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
import org.isoron.uhabits.views.HabitFrequencyView;
|
||||
|
||||
@@ -34,8 +35,8 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitFrequencyView view = new HabitFrequencyView(context, null);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
HabitFrequencyView dataView = new HabitFrequencyView(context);
|
||||
GraphWidgetView view = new GraphWidgetView(context, dataView);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
@@ -67,6 +68,6 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
return R.layout.widget_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.GraphWidgetView;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
|
||||
@@ -33,9 +34,9 @@ public class HistoryWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitHistoryView view = new HabitHistoryView(context, null);
|
||||
HabitHistoryView dataView = new HabitHistoryView(context);
|
||||
GraphWidgetView view = new GraphWidgetView(context, dataView);
|
||||
view.setHabit(habit);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -54,18 +55,18 @@ public class HistoryWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
return 250;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
return 250;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
return R.layout.widget_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,25 @@ import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.GraphWidgetView;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
import org.isoron.uhabits.views.HabitScoreView;
|
||||
|
||||
public class ScoreWidgetProvider extends BaseWidgetProvider
|
||||
public class ScoreWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitScoreView view = new HabitScoreView(context, null);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
int defaultScoreInterval = UIHelper.getDefaultScoreInterval(context);
|
||||
int size = HabitScoreView.DEFAULT_BUCKET_SIZES[defaultScoreInterval];
|
||||
|
||||
HabitScoreView dataView = new HabitScoreView(context);
|
||||
dataView.setIsTransparencyEnabled(true);
|
||||
dataView.setBucketSize(size);
|
||||
|
||||
GraphWidgetView view = new GraphWidgetView(context, dataView);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
@@ -54,18 +62,18 @@ public class ScoreWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
return 300;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
return 300;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
return R.layout.widget_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.GraphWidgetView;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
import org.isoron.uhabits.views.HabitStreakView;
|
||||
|
||||
@@ -33,8 +34,8 @@ public class StreakWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitStreakView view = new HabitStreakView(context, null);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
HabitStreakView dataView = new HabitStreakView(context);
|
||||
GraphWidgetView view = new GraphWidgetView(context, dataView);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
@@ -66,6 +67,6 @@ public class StreakWidgetProvider extends BaseWidgetProvider
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
return R.layout.widget_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_add_light.png
Normal file
|
After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 354 B |
BIN
app/src/main/res/drawable-hdpi/ic_alarm_black.png
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
app/src/main/res/drawable-hdpi/ic_alarm_white.png
Normal file
|
After Width: | Height: | Size: 610 B |