Compare commits

...

148 Commits

Author SHA1 Message Date
2b6fc06b86 Merge branch 'hotfix/1.5.6' 2016-06-19 17:10:12 -04:00
98ad3aab9d Make checkmark widgets work again 2016-06-19 17:06:03 -04:00
c7e63a40da Bump version to 1.5.6 2016-06-19 17:04:25 -04:00
c2dd26eeb3 Merge branch 'hotfix/1.5.5' 2016-06-19 10:08:19 -04:00
5831340343 Update French translation; add Slovenian and Croatian translations 2016-06-19 09:57:45 -04:00
d7f6f52a49 Always refresh data after resuming
Fixes #120
2016-06-19 09:32:42 -04:00
93b442332d Bugfix: add check on correct date when checking from notification 2016-06-19 09:08:34 -04:00
e248824bcd Bump version to 1.5.5 2016-06-19 08:08:00 -04:00
00774368d4 Merge branch 'hotfix/1.5.4' 2016-05-29 12:06:36 -04:00
cbf1bd3e19 Update changelog 2016-05-29 11:57:33 -04:00
4061921b93 Add Serbian translation 2016-05-29 11:56:57 -04:00
59d42fe62f Show empty ringtone name in case of RuntimeException
Fixes #116
2016-05-29 11:47:36 -04:00
88e8aad0d8 CSV export: sanitize habit name before creating folder
Fix #113
2016-05-25 10:09:39 -04:00
a4b6728721 Bump version to 1.5.4 2016-05-25 10:09:11 -04:00
33bc8f78e7 Merge branch 'hotfix/1.5.3' 2016-05-22 12:56:28 -04:00
0f223f8504 Update changelog 2016-05-22 12:56:15 -04:00
007996c69e Update translations 2016-05-22 12:50:42 -04:00
ff9d50b32a Remove debug code 2016-05-22 12:29:37 -04:00
38024d71ce Allow checkmark widget to be resized to very small sizes 2016-05-22 12:24:28 -04:00
9ec1afc208 Remove associated preference after deleting widget 2016-05-22 10:28:58 -04:00
e0888a9b4d Remove fields from BaseWidgetProvider
This caused a bug when multiple widgets had different sizes. The size of the
last widget would be used in all of them.
2016-05-22 10:27:27 -04:00
8aade2f145 Fix crash when habits with reminders have short names
Fixes #109
2016-05-22 09:33:30 -04:00
1ba1d775f7 Bump version to 1.5.3 2016-05-22 09:24:20 -04:00
bd6cba8303 Merge branch 'hotfix/1.5.2' 2016-05-19 12:01:29 -04:00
befc3a0ad8 Update changelog 2016-05-19 12:01:24 -04:00
f4c963e2c1 Update Japanese translation 2016-05-19 11:58:20 -04:00
e9a4a047c1 Trim habit names on log files 2016-05-19 11:50:38 -04:00
7ce1988d2e Fix NullPointerException on RingView 2016-05-19 11:46:02 -04:00
53911fa410 Send bug report on the body of email, instead of attachment 2016-05-19 11:39:17 -04:00
f5f43d9a16 Bump version to 1.5.2 2016-05-19 10:40:45 -04:00
d0e475ad78 Merge branch 'hotfix/1.5.1' 2016-05-17 09:35:51 -04:00
b3199cf092 Update version and changelog 2016-05-17 09:35:10 -04:00
73e6f2a2d4 Remove ActiveAndroid.jar 2016-05-17 09:29:53 -04:00
097a5be864 Merge branch 'release/1.5.0' 2016-05-15 08:43:26 -04:00
0287473e61 Update changelog 2016-05-15 08:40:58 -04:00
f403dc6f77 Update view tests 2016-05-15 08:40:27 -04:00
23466523df Remove minTextSize
This was causing problems, since some launchers completely
ignore minimum widget sizes. When widgets were too small,
the text started to overflow.
2016-05-15 08:21:46 -04:00
83ca136346 Update changelog 2016-05-13 07:09:09 -04:00
7830b599db Bump version to 1.5.0 2016-05-13 06:52:32 -04:00
49376d4f41 Minor changes to widget colors 2016-05-13 06:50:35 -04:00
8ee9c9c0b1 Update screenshots 2016-05-13 06:50:22 -04:00
ee9da29422 Update introduction 2016-05-13 06:42:22 -04:00
ae7b0408b2 Update list of complete translations 2016-05-13 06:29:19 -04:00
d926d9dbd6 Keep name of the app consistent across translations 2016-05-13 06:14:40 -04:00
5694da2413 Add new translations and update list of contributors 2016-05-13 06:08:09 -04:00
7e0ae144b8 Update translations 2016-05-13 04:56:02 -04:00
1426d887e7 CircleCI: Ignore some branches 2016-05-13 04:41:15 -04:00
acb8e820fd Use AlertDialog from Support Library 2016-05-11 07:38:43 -04:00
eae0d66f51 Dismiss notification automatically when user adds a checkmark 2016-05-11 07:30:45 -04:00
e933cbbc43 Fix up navigation when opening the app from widget
Fixes #94
2016-05-11 06:44:11 -04:00
0b95b6a78c Install build-tools before compiling 2016-05-08 08:51:08 -04:00
5e873a3659 Make frequency text more natural 2016-05-08 08:46:45 -04:00
d292ecd988 Increase default amount of memory for gradle 2016-05-08 08:24:14 -04:00
19b484c368 Fix crash on settings screen 2016-05-08 08:23:50 -04:00
834ae92d87 Reschedule alarms on boot
Fixes #93
2016-05-08 08:04:24 -04:00
26ce92d381 Update versions 2016-05-08 07:38:57 -04:00
0eb12812d4 Allow user to reverse the order of days 2016-05-04 06:30:06 -04:00
a1a636c718 Make ScoreView use custom interval also on widgets 2016-05-01 10:06:02 -04:00
b2811b9797 Allow user to select reminder sound
Closes #63
2016-05-01 09:43:28 -04:00
4db7a6e89c Show larger ripple when toggling check marks
Closes #78
2016-05-01 08:21:36 -04:00
677a643e5b Allow theme to be fixed during tests 2016-04-30 19:13:36 -04:00
dbca3238f6 Small changes to ScoreView 2016-04-30 17:14:44 -04:00
ae5dd700f3 Fix widget colors and size 2016-04-30 16:42:27 -04:00
7e4c508d7d Merge branch 'feature/ring-view' into dev 2016-04-30 12:44:25 -04:00
d0c056eeaa Update screenshots 2016-04-30 12:43:18 -04:00
f172c69eed Update tests 2016-04-30 12:30:20 -04:00
0cd4a41438 Bump gradle version 2016-04-29 11:48:46 -04:00
d77c78249c Tweak layout to work with older phones with smaller screen 2016-04-29 11:48:06 -04:00
d9d48ff984 Minor UI changes 2016-04-29 07:55:45 -04:00
7be71ba4f8 Fix icon colors and remove some warnings 2016-04-29 07:44:06 -04:00
f0534fefbc Show more information on score overview 2016-04-29 07:35:38 -04:00
30ef75bb45 Alternative design for header bar 2016-04-29 06:40:02 -04:00
44ed74a693 Correct colors 2016-04-29 06:38:19 -04:00
4aea527e07 Fix some crashes on widgets 2016-04-27 06:01:10 -04:00
638539259a Bump gradle version 2016-04-26 20:09:06 -04:00
75dec88411 Update widget previews 2016-04-26 20:08:59 -04:00
2dc4fcbc46 Fix transparency on widgets 2016-04-25 22:22:37 -04:00
7977d5247c Tweak transparency and colors 2016-04-25 21:03:23 -04:00
8b18e32a16 Create color palette for widgets 2016-04-24 06:42:14 -04:00
dbcad9a5f4 Use dark theme for widget colors 2016-04-24 06:42:14 -04:00
3bba75ff50 Add ripple effect to check mark widget 2016-04-24 06:42:14 -04:00
9bbeee66e2 Impose max number of lines on habit list 2016-04-24 06:42:14 -04:00
c6e76f4cfd Show multiple lines on check mark widget 2016-04-24 06:42:14 -04:00
980db1d171 Add ring to check mark widget 2016-04-24 06:42:14 -04:00
aee5d975db Replace star by ring 2016-04-24 06:42:14 -04:00
e2bb4371d3 Fix UI test for settings screen 2016-04-24 06:37:15 -04:00
fcee8552f0 Switch to compat ActionMode; fix tests 2016-04-24 05:55:42 -04:00
a4864e4612 Extract some strings; move definitions to correct file
Fixes #81
2016-04-21 06:05:38 -04:00
8ddf4574e3 Fix failed view tests 2016-04-20 09:27:44 -04:00
f1ca8c5449 Merge branch 'feature/material-compat' into dev 2016-04-20 07:52:06 -04:00
81a188f125 Customize action mode colors 2016-04-20 07:50:12 -04:00
44402bf3a4 Fix progress bar position 2016-04-20 07:31:02 -04:00
7f159149ef Minor layout changes on the spinners 2016-04-19 22:50:41 -04:00
0c1e8d5131 Switch to AppCompatDialogFragment and compat Fragment 2016-04-19 22:10:25 -04:00
a4e3f7e037 Fix spinners on EditHabitDialog 2016-04-19 21:53:14 -04:00
b354a0765b Use grey toolbar on pre-Lollipop 2016-04-19 21:02:05 -04:00
bbd959dfda Fix borders on HistoryEditorDialog 2016-04-19 09:01:36 -04:00
d05d404c55 Show toolbar shadow on pre-Lollipop 2016-04-19 08:27:45 -04:00
8938b0c9a6 Improve settings screen 2016-04-19 07:50:05 -04:00
bd6fcd066c Fix dialog borders 2016-04-19 05:36:34 -04:00
6a5f2abb76 Switch to AppCompatActivity 2016-04-18 07:59:41 -04:00
33b5215b00 Use SwitchPreference instead of checkmarks 2016-04-18 06:21:04 -04:00
5d808dd2e8 Update padding on widgets 2016-04-16 09:20:33 -04:00
767ec1b6de Minor layout improvements for HabitScoreView 2016-04-16 09:16:39 -04:00
9e5f3d8f58 Fix blinking when updating widgets 2016-04-16 09:03:45 -04:00
775d028629 Add missing icon 2016-04-16 08:51:56 -04:00
4bfb839370 Draw separate widgets for landscape and portrait modes
Fixes #76
2016-04-16 08:51:20 -04:00
8f64b696d8 Remove debug messages 2016-04-16 08:10:12 -04:00
c4bf31e778 Adjust text size and position on HistoryView and StreakView 2016-04-16 08:06:01 -04:00
c757ce6548 Enforce min text size on HabitScoreView 2016-04-16 07:36:39 -04:00
78994bb3c4 Add screenshots for night mode 2016-04-15 19:12:45 -04:00
2373ba5407 Update screenshots 2016-04-15 19:10:38 -04:00
b601a643dd Merge branch 'feature/dark-theme' into dev 2016-04-15 18:29:39 -04:00
90e513e778 Remove warning when palette cannot be found 2016-04-15 18:26:01 -04:00
728c9557f0 Migrate DB to new color format 2016-04-15 18:25:30 -04:00
5bd0c842f5 Minor typo 2016-04-15 18:10:34 -04:00
97711087f9 Fix invisible text on widgets 2016-04-15 17:49:10 -04:00
2fe2d8834a Fix spinner theme on pre-lollipop 2016-04-15 17:41:16 -04:00
93a893d660 Replace icons with Material icons
Fixes #9
2016-04-15 16:34:22 -04:00
feff9a18e6 Fix action icons 2016-04-15 16:13:52 -04:00
b42565b770 Backport dark theme to pre-lollipop devices 2016-04-13 11:54:24 -04:00
3de702ced2 Fix colors in About screen 2016-04-13 10:44:11 -04:00
42f7f4042d Add option for AMOLED night mode 2016-04-13 10:38:20 -04:00
2115a590f2 Add fade transition when switching themes 2016-04-13 09:53:37 -04:00
f5f3ec9f88 Update tests for custom views 2016-04-12 23:42:02 -04:00
6d5a8f5753 Merge branch 'sciamano-day-of-week' into dev 2016-04-12 23:29:10 -04:00
06fc04092b Code cleanup 2016-04-12 23:21:00 -04:00
Denis
24c02605d1 Select first day of week according to the current locale 2016-04-12 23:11:32 -04:00
11fcb67624 Improve custom views' colors 2016-04-11 18:11:18 -04:00
abc92e390a Update list of translators 2016-04-10 12:59:42 -04:00
52c07660b1 Implement menu item to switch between themes 2016-04-10 10:41:52 -04:00
35f778c376 Do not use habit color as primary color on dark themes 2016-04-10 08:32:01 -04:00
436ae7e574 Update selected_box colors for dark theme 2016-04-10 08:17:36 -04:00
5c683a77c2 Fix ripple on ListHabitsFragment 2016-04-10 08:13:05 -04:00
dd1f6c9efc Change habit.color palette according to current theme 2016-04-09 20:01:35 -04:00
cba089407b Merge tag 'v1.4.1' into dev
Loop 1.4.1
2016-04-09 05:36:12 -04:00
a7b0395a2a Merge branch 'hotfix/1.4.1' 2016-04-09 05:36:05 -04:00
7d2946360f Update changelog 2016-04-09 05:31:07 -04:00
04e8432522 Widgets: show error message instead of crashing 2016-04-09 05:27:39 -04:00
13d34945b4 Merge branch 'poeditor' into hotfix/1.4.1 2016-04-09 05:10:41 -04:00
756578508e Bump version to 1.4.1 2016-04-09 05:10:20 -04:00
38fc7650b9 Update translations 2016-04-09 05:08:02 -04:00
cf06ec0a4b Fix chart colors 2016-04-09 04:58:26 -04:00
f0701f7b35 First version of dark theme 2016-04-08 17:15:14 -04:00
de8018af7d Delete unused resources 2016-04-08 15:53:47 -04:00
ac885e1503 Refactor ListHabitsFragment layouts and styles 2016-04-08 15:50:51 -04:00
f85e09288c Add link to open beta on Google Play 2016-04-07 18:03:46 -04:00
6f11596bb7 Merge tag 'v1.4.0' into dev
Loop 1.4.0
2016-04-07 17:48:50 -04:00
247 changed files with 5720 additions and 1865 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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'
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -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);
}

View File

@@ -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()));
}
}

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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");
}

View File

@@ -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");
}
}
}

View File

@@ -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");

View File

@@ -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());
}

View File

@@ -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="22"
android:versionName="1.5.6">
<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>

View 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;

View File

@@ -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;

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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,29 @@ 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_UPDATE_CURRENT);
PendingIntent dismissPendingIntent = buildDismissIntent(context);
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
PendingIntent checkIntentPending = buildCheckIntent(context,
habit, timestamp, 1);
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 +198,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 +207,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,31 +224,38 @@ 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_UPDATE_CURRENT);
}
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
public static PendingIntent buildCheckIntent(Context context, Habit
habit, Long timestamp, int requestCode)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK);
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
return PendingIntent.getBroadcast(context, requestCode, checkIntent,
PendingIntent.FLAG_UPDATE_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_UPDATE_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 +268,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);
}
}

View File

@@ -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;
}
}

View File

@@ -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")));

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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";

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);

View 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;
}
}

View File

@@ -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();
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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 };
}
}

View File

@@ -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;
}
@@ -47,25 +47,25 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null, 2);
}
@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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Some files were not shown because too many files have changed in this diff Show More