Compare commits

...

352 Commits

Author SHA1 Message Date
64c4367706 Merge branch 'release/1.4.0' 2016-04-07 17:48:33 -04:00
c08702341a Update Spanish translations 2016-04-07 17:23:01 -04:00
abb8c09bc9 Update widgets after executing command 2016-04-07 17:15:33 -04:00
87255ceb25 RingView: invalidate after updating percentage and color
Fixes #79
2016-04-07 17:02:02 -04:00
aedbfded0f Show POEditor link if language is not fully translated 2016-04-06 06:33:52 -04:00
779c0040bd Add some options back to settings screen 2016-04-05 13:32:46 -04:00
7bba6a887f Increase width of chart labels as needed 2016-04-05 08:11:37 -04:00
52a1d19793 Allow labels to expand 2016-04-05 06:50:42 -04:00
35e346e7ea Remove empty strings from translations 2016-04-05 06:42:32 -04:00
3102158bdb Merge branch 'poeditor' into release/1.4.0 2016-04-05 06:34:00 -04:00
7afa65a53c Add Czech translation 2016-04-05 06:33:48 -04:00
2e5533d480 Update translations (POEditor) 2016-04-05 06:28:35 -04:00
b29c7e695a Save failed views to SD card root if writable 2016-04-05 05:47:09 -04:00
7df745eb82 Update CHANGELOG 2016-04-04 20:49:57 -04:00
c0d518ca4d Bump version to 1.4.0 2016-04-04 19:48:49 -04:00
eeff14a2ca Bump version to 1.4.0 2016-04-04 19:31:35 -04:00
af3b18f518 Fix incorrect date format for some languages 2016-04-04 19:05:24 -04:00
3811f4b339 Rename tests for compatibility with AWS Device Farm 2016-04-04 11:15:17 -04:00
50a8aece6f Move expensive call to background thread 2016-04-04 08:18:59 -04:00
2f66cfc417 Minor string change 2016-04-04 08:07:10 -04:00
132ce36a57 More detailed logs in case getFilesDir fails 2016-04-04 07:58:07 -04:00
147f010d1b Update website URL 2016-04-04 07:57:28 -04:00
afc91ed89a Merge branch 'feature/performance' into dev 2016-04-04 07:04:22 -04:00
684c37d167 Update list of translators 2016-04-04 07:02:41 -04:00
8b296ce2ae Make setup method public 2016-04-04 06:56:02 -04:00
e2e9e2e27a Remove debug code 2016-04-04 06:54:12 -04:00
8b2f31c44a Update widget preview 2016-04-04 06:53:55 -04:00
83db9fe2f2 Hide progress bar on startup 2016-04-04 06:46:16 -04:00
9891139b5a Fix tests on ICS 2016-04-04 06:43:42 -04:00
cbc8cbfea3 Fix scores with null habit 2016-04-04 06:02:56 -04:00
96c46b655d Minor refactoring 2016-04-04 05:43:07 -04:00
0de52d4fa3 BaseTask: move register/unregister to onPre/PostExecute 2016-04-04 05:42:27 -04:00
16d85dc4c7 Update README.md 2016-04-03 12:41:57 -04:00
c51b1fd399 Remove progress bar; switch to BaseTask 2016-04-03 07:51:54 -04:00
eb8dd1d450 Simplify and optimize loading time of ShowHabitFragment 2016-04-03 07:46:08 -04:00
357646152f Use postInvalidate instead of invalidate 2016-04-03 07:27:37 -04:00
ae5be202b2 Add convenience methods for tracing 2016-04-03 07:27:16 -04:00
cc3e2153d0 Allow BaseTask to publish integer progress 2016-04-03 07:26:40 -04:00
7433a2413d Refactor BaseTask interface 2016-04-03 06:45:10 -04:00
1cd8eb6849 NumberView: do not request layout on setNumber 2016-04-03 05:11:45 -04:00
7f009e2365 Create indexes in database 2016-04-03 05:10:13 -04:00
4cec7d7367 Implement methods to generate huge test database 2016-04-03 05:08:57 -04:00
f59c0bd7ff Replace badges 2016-04-02 13:58:38 -04:00
2d2bb8b4ed Render widgets in background 2016-04-02 13:32:04 -04:00
e476096ae1 Fix view tests 2016-04-02 11:47:28 -04:00
e2c814a982 Fix ShowHabitFragment and HistoryEditorDialog 2016-04-02 10:48:33 -04:00
4fcaa68baf Throw exception when slow methods are executed on the main thread 2016-04-02 10:27:45 -04:00
02e45dbe27 Rename DialogHelper to UIHelper 2016-04-02 10:14:21 -04:00
bf6562f854 Update views on background thread 2016-04-01 17:50:14 -04:00
3c927e009a Improve performance of Score computation 2016-04-01 17:49:17 -04:00
a9bcae0f2f Clean up CheckmarkView 2016-04-01 17:24:12 -04:00
bc54f3f916 Improve performance of toggleCheckmark 2016-04-01 16:21:22 -04:00
5763d76167 Make setup method public 2016-04-01 16:08:51 -04:00
86b66b1388 Add tests for DeleteHabitsCommand 2016-03-31 08:02:44 -04:00
5e0422848b Add tests for EditHabitCommand 2016-03-31 07:54:00 -04:00
a67a3297b1 Add tests for ToggleRepetitionCommand 2016-03-31 07:34:20 -04:00
b2f2d4ad28 Add tests for UnarchiveHabitsCommand 2016-03-31 07:24:43 -04:00
4dc8201dcb Add tests for CreateHabitCommand 2016-03-31 07:20:20 -04:00
f24e300b40 Implement tests for Archive and ChangeColor commands 2016-03-31 07:11:24 -04:00
21157eaa19 Increase similarity threshold 2016-03-31 07:09:47 -04:00
0ea2c8e5c8 HistoryView: test tap at invalid locations 2016-03-31 06:31:13 -04:00
826491bc90 Test custom views with transparent background 2016-03-31 06:21:01 -04:00
72f5ca5531 Add tests for NumberView 2016-03-31 06:03:16 -04:00
f9da90a93a Add tests for HabitStreakView 2016-03-30 21:02:05 -04:00
96c6a97ad0 Add tests for HabitScoreView 2016-03-30 19:46:37 -04:00
20469789ea Add tests for HabitFrequencyView 2016-03-30 18:41:00 -04:00
abafffcff1 Increase similarity threshold 2016-03-30 12:25:45 -04:00
d45a4445cc Minor changes to HabitHistoryView rendering; enable haptic feedback 2016-03-30 12:25:24 -04:00
94f56a8869 Make all unit tests inherit from BaseTest 2016-03-30 08:49:42 -04:00
bc331d0a4d Write tests for HabitHistoryView 2016-03-30 08:28:21 -04:00
2ba7246e5f Refactor Tasks for better testability 2016-03-30 08:28:06 -04:00
67b88c5012 Show log when build fails 2016-03-30 08:24:42 -04:00
e0b637e84d Merge branch 'feature/unit-test-views' into dev 2016-03-28 21:14:42 -04:00
e14d73cf32 Add translator 2016-03-28 21:00:07 -04:00
16b9584868 Minor changes to strings.xml 2016-03-28 20:20:18 -04:00
c091116105 Compute padding/margins more precisely on CheckmarkView 2016-03-28 20:06:32 -04:00
37e1a6233c Copy failed generated images to CircleCI artifacts folder 2016-03-27 18:47:26 -04:00
6838a0f2d3 Restore missing asset 2016-03-27 06:40:56 -04:00
53e737c169 Allow API version-specific view tests; use relative image distance 2016-03-27 06:39:40 -04:00
285f79ed95 Add tests for CheckmarkView 2016-03-26 20:11:17 -04:00
4f65c8bef1 Add script to pull failed generated images from device 2016-03-26 19:47:53 -04:00
51b7cfc8fa Remove useless asset 2016-03-26 19:47:04 -04:00
55ed2585ea Additional tests for RingView 2016-03-26 19:46:33 -04:00
2c62e1578a Create base test class for custom views 2016-03-26 19:25:50 -04:00
e3d96c0431 Upload code coverage to codecov.io 2016-03-26 17:03:21 -04:00
b4c2d2237a Draw month name on the following column.
This reverts commit f9377e17. See #55 for reason.
2016-03-26 08:57:18 -04:00
d14fbbd130 Make streak colors consistent 2016-03-26 08:49:02 -04:00
e2c99d745e Improve visualization of streaks
Closes #20
2016-03-26 08:27:23 -04:00
dfe41176bc Reintroduce vibrate permission 2016-03-25 17:01:41 -04:00
1ef4f8d456 Use system haptic feedback instead of custom vibration; enable on short press
Closes #66
2016-03-25 14:39:57 -04:00
6c810ee7a3 Allow user to send bug report from settings screen 2016-03-25 14:39:57 -04:00
5115379fdd Merge branch 'feature/import-data' into dev 2016-03-25 09:21:47 -04:00
d176ea91fb Use custom matcher for settings activity 2016-03-25 09:04:07 -04:00
62df660079 Implement UI tests for import/export and help 2016-03-25 08:10:10 -04:00
790beb185a Do not save backup when importing Loop DB 2016-03-25 08:09:33 -04:00
59c0af17d7 Use application context to initialize ActiveAndroid 2016-03-25 07:09:41 -04:00
28dad560a6 Write tests for CSV exporter 2016-03-25 07:09:06 -04:00
9e410f8395 Refactor and write tests for IO tasks 2016-03-25 06:18:07 -04:00
3656c51e95 Disable pre-dexing 2016-03-24 22:14:54 -04:00
d3ebb4ff25 Create SD card 2016-03-24 21:52:59 -04:00
3c595bc79d Add null check on DatabaseHelper 2016-03-24 21:51:02 -04:00
1120f56dd4 Write tests for CSV export 2016-03-24 21:36:41 -04:00
2dfbcfcdb0 Revert to alpha version of gradle
Build fails otherwise
2016-03-24 20:14:26 -04:00
90a3964f0f Close database 2016-03-24 18:39:49 -04:00
6c05366f0f Remove illegal characters from filename 2016-03-24 18:39:32 -04:00
dcc771abe7 Chose external dir better 2016-03-24 18:39:14 -04:00
743431ef67 Refactor DatabaseHelper; write tests for data import 2016-03-24 06:59:41 -04:00
d5fc1a6886 Allow user to import full database
Closes #67
2016-03-23 19:21:06 -04:00
eeb0b109ae Remove export CSV from context menu 2016-03-23 19:20:12 -04:00
ad391fa791 Remove extra permissions; better organize files dir 2016-03-23 18:50:02 -04:00
e8bbae8ef9 Allow user to export a full copy of the database 2016-03-23 18:01:25 -04:00
c9793df7c7 Reorganize files 2016-03-23 17:20:26 -04:00
3c6114fc87 Update README.md 2016-03-23 14:19:30 -04:00
4ea4201b6a Merge pull request #69 from XuToTo/patch-1
Update strings.xml
2016-03-23 14:10:42 -04:00
XuToTo
30b5b90091 Update strings.xml
Make some zh-translation words more friendly :)
2016-03-24 00:53:37 +08:00
e6b7b8b590 Export all habits as CSV from the settings menu
Closes #28
2016-03-22 22:54:02 -04:00
2d675ed9b0 Use DB transaction to perform import 2016-03-22 22:54:02 -04:00
1db2f69f05 Import data from HabitBull
Closes #44
2016-03-22 22:54:02 -04:00
49a80faca3 Trap all exceptions 2016-03-22 22:54:02 -04:00
aa5df56437 Use stable version of gradle 2016-03-22 22:54:01 -04:00
581197be03 Import data from Tickmate and Rewire
Closes #36, closes #41
2016-03-22 22:53:16 -04:00
dfe5c4954e Refactor CSVExporter 2016-03-21 06:25:48 -04:00
a0582b65d5 Merge tag 'v1.3.3' into dev
Version 1.3.3
2016-03-20 17:55:16 -04:00
9edc3e12c9 Merge branch 'hotfix/1.3.3' 2016-03-20 17:55:07 -04:00
7bb3db213f Update changelog 2016-03-20 17:46:10 -04:00
e05a69b527 Merge branch 'poeditor' into hotfix/1.3.3 2016-03-20 17:31:08 -04:00
d5df6ddcdb Add Spanish and Korean translations
Closes #40
2016-03-20 17:30:55 -04:00
3016263750 Update translations 2016-03-20 17:25:06 -04:00
f9377e1768 Draw month name on the correct column
Fixes #55
2016-03-20 17:10:57 -04:00
ae0dad9120 Use DateHelper instead of instantiating GregorianCalendar directly
Fixes #58
2016-03-20 17:06:22 -04:00
be114bde7f Bump version to 1.3.3 2016-03-20 17:01:58 -04:00
e7148abc2e Open statistics page when user taps on widget
Closes #19
2016-03-20 08:36:16 -04:00
2d87076a48 Change the way score are grouped 2016-03-20 08:19:57 -04:00
7da4ddf91b Allow user to change score view interval
Closes #10
2016-03-20 08:06:12 -04:00
e4e8d77acc Add link to FAQ
Closes #24
2016-03-19 13:30:04 -04:00
7945a5faf9 Set default habit frequency to daily 2016-03-19 11:58:25 -04:00
3f7d25461d Wake up device in Doze mode 2016-03-19 10:19:28 -04:00
519737a1c3 Minor changes to English strings 2016-03-19 10:02:49 -04:00
c3ff1fbe03 Add quick selection for commonly used habit frequencies
Closes #25
2016-03-19 09:46:42 -04:00
d39e1978a2 Add Spanish and Korean translations
Closes #40
2016-03-19 05:03:23 -04:00
536c095f23 Merge tag 'v1.3.2' into dev
Version 1.3.2
2016-03-18 11:36:48 -04:00
8aaa5aca28 Merge branch 'hotfix/1.3.2' 2016-03-18 11:35:24 -04:00
5540a66e08 Minor spelling change 2016-03-18 11:34:05 -04:00
01ffc6c144 Update changelog 2016-03-18 11:25:57 -04:00
21aa658acc Fix crash when input is empty 2016-03-18 11:25:34 -04:00
e17667d85d Fix small layout issue on RingView 2016-03-18 11:25:19 -04:00
f2948dac6f Bump version to 1.3.2 (12) 2016-03-18 11:12:33 -04:00
31a7d92f77 Update translations 2016-03-18 11:11:04 -04:00
82d7970f86 Update list of translators 2016-03-18 11:11:04 -04:00
af1a21a959 Add Swedish translation 2016-03-18 11:11:04 -04:00
e7c1575083 Add Russian translation 2016-03-18 11:11:04 -04:00
d5a14ca55b Add Polish translation 2016-03-18 11:11:04 -04:00
c4d6b80944 Add Italian translation 2016-03-18 11:11:04 -04:00
c4b6cad6bb Update German translation 2016-03-18 11:11:03 -04:00
7070530d0e Update French translation 2016-03-18 11:11:03 -04:00
1332a70eb4 Declare RTL support in the manifest 2016-03-18 11:11:03 -04:00
11c9a3dab0 Add Arabic translation 2016-03-18 11:11:03 -04:00
74f7b0fd28 Update translators' names 2016-03-18 11:11:03 -04:00
e693504183 Update translations 2016-03-18 11:01:25 -04:00
7d9a94ae9e Add line to disable large tests 2016-03-18 10:58:18 -04:00
326cb8f73f Minor changes to javadoc and method visibility 2016-03-18 10:42:47 -04:00
6826bd1ddc Update test 2016-03-18 10:36:56 -04:00
b5bc347624 Use default instead of null for reminderDays 2016-03-18 07:43:11 -04:00
55c058ff42 Minor spelling mistakes 2016-03-18 07:24:26 -04:00
e829bb8109 Enable parallel and daemon (gradle) 2016-03-18 07:18:50 -04:00
0921f9346e Refactor and write docs for Score and ScoreList 2016-03-18 07:16:31 -04:00
eb017bf99b Write missing tests and docs for RepetitionList 2016-03-17 06:49:11 -04:00
79b6ef8200 Improve null check for Checkmark and CheckmarkList 2016-03-17 06:39:29 -04:00
7ba62d6784 Minor formatting 2016-03-17 06:39:12 -04:00
f5e4a88415 Implement missing tests for Habit; remove some dead code 2016-03-17 06:20:52 -04:00
075b7812eb Refactor and write documentation for Habit 2016-03-16 07:49:40 -04:00
3d42505fb9 Merge tag 'v1.3.1' into dev 2016-03-15 20:38:51 -04:00
824f98dd96 Merge branch 'hotfix/1.3.1' 2016-03-15 20:31:06 -04:00
fcb82bcb72 Update changelog for v1.3.1 2016-03-15 20:30:01 -04:00
851cae3662 Show error message on widget when habit not found
Fixes #35
2016-03-15 20:25:47 -04:00
7778c5fb21 Check for null on notifications 2016-03-15 20:17:15 -04:00
ef847dac17 Use StaticLayout to draw RingView label
Fixes #29
2016-03-15 19:56:14 -04:00
8102c18c67 Use long for millisecondsInOneDay
Fixes #34
2016-03-15 19:55:50 -04:00
59ed9ec9bd Bump version to 1.3.1 2016-03-15 19:51:50 -04:00
ffdc923268 Make labels more clear and customizable 2016-03-15 06:10:39 -04:00
9232378d04 Refactor RingView; make text size consistent 2016-03-15 05:30:27 -04:00
b20fd44cbc Scroll to view before clicking 2016-03-14 20:48:39 -04:00
ded8800017 Extract common style 2016-03-14 19:36:05 -04:00
606f66b8d9 Merge branch 'feature/repetition-count' into dev 2016-03-14 19:30:58 -04:00
1bb6cd405b Externalize strings 2016-03-14 19:13:33 -04:00
babf7d64f0 Display repetition count for last week, month, etc
Closes #21
2016-03-14 19:07:32 -04:00
dfbcf78dd7 Update list of translators 2016-03-14 15:46:52 -04:00
e01c668e4d Add Swedish translation 2016-03-14 15:46:40 -04:00
2eef696027 Add Russian translation 2016-03-14 15:46:30 -04:00
ddd10cacd1 Add Polish translation 2016-03-14 15:38:42 -04:00
0cdde4901e Add Italian translation 2016-03-14 15:38:29 -04:00
f13e9b7362 Update German translation 2016-03-14 15:38:12 -04:00
558f72d7c5 Update French translation 2016-03-14 15:33:06 -04:00
6e493f55bc Declare RTL support in the manifest 2016-03-14 15:18:58 -04:00
5186ab840a Add Arabic translation 2016-03-14 15:11:41 -04:00
65fd82d888 Fix indentation 2016-03-14 15:04:15 -04:00
866b62987c Wake up device before running UI tests 2016-03-14 14:55:47 -04:00
18abb2038f Check if habit is null on BaseWidgetProvider 2016-03-14 14:54:43 -04:00
f7f4b5eeb0 Simplify code for drawing header 2016-03-14 14:35:57 -04:00
45a7433773 Use StaticLayout to draw RingView label
Fixes #29
2016-03-14 13:34:21 -04:00
40420c3a77 Save logcat to reports 2016-03-14 08:37:02 -04:00
988b39f2e5 Add missing debug messages to SystemHelper 2016-03-14 08:33:46 -04:00
a0803966f9 Merge branch 'feature/unit-tests' into dev 2016-03-14 08:14:56 -04:00
e4af662836 Merge pull request #33 from vanniktech/patch-1 2016-03-14 08:10:10 -04:00
e95adab422 Include CircleCI badge 2016-03-14 07:39:43 -04:00
012fed01eb Fix CircleCI; remove Travis-CI 2016-03-14 07:39:43 -04:00
4cf2b8072b Unlock screen before running UI tests 2016-03-14 07:39:43 -04:00
196a79a88c Add CircleCI config file 2016-03-14 07:39:43 -04:00
4b7e2e79a7 Update tools versions 2016-03-14 07:39:43 -04:00
9156bba267 Disable animations when testing 2016-03-14 07:39:43 -04:00
1a18bb939d Refactor and write unit tests for RepetitionList 2016-03-14 07:39:43 -04:00
144524e53b Refactor and write tests for checkmarks 2016-03-14 07:39:43 -04:00
3d1c53396c Allow date to be fixed at a certain timestamp 2016-03-14 07:39:43 -04:00
1930db3cd1 Wait after toggling checkmarks 2016-03-14 07:39:43 -04:00
a2c2a5531a Use temporary database for tests 2016-03-14 07:39:43 -04:00
eee2605f74 Add first unit tests for habit 2016-03-14 07:39:43 -04:00
Niklas Baudy
9df0c9ae9e Update EditText of Edit Habit Description to capitalize sentences 2016-03-14 10:19:47 +01:00
e0533505c1 Merge pull request #30 from vanniktech/patch-1
Update EditText of Edit Habit to capitalize sentences
2016-03-14 04:41:09 -04:00
Niklas Baudy
433894336c Update EditText of Edit Habit to capitalize sentences
Basically when entering a word the first letter will be capitalized. Something minor that bugs me always a bit.
2016-03-13 23:46:29 +01:00
9061182301 Update translators' names 2016-03-12 19:39:03 -05:00
8c45f52d56 Merge tag 'v1.3.0' into dev
Release v1.3.0
2016-03-12 06:03:02 -05:00
53c9dca3e2 Merge branch 'release/1.3.0' 2016-03-12 06:02:22 -05:00
8b37daa9fb Update CHANGELOG 2016-03-12 06:02:11 -05:00
f9c2e83c79 Update screenshots 2016-03-12 05:37:00 -05:00
71bdc70c1a Bump version to 1.3.0 2016-03-12 05:25:10 -05:00
5e4a40579a Put about under link category on settings screen 2016-03-12 05:23:34 -05:00
d326be1224 Reintroduce longClick hack 2016-03-12 05:17:55 -05:00
fb8a09c95c Add German translation 2016-03-12 05:13:11 -05:00
1635b9905d Split test 2016-03-12 05:03:04 -05:00
2d88fc0b20 Update Japanese translation 2016-03-11 16:27:40 -05:00
1d74359c06 Update widgets in background 2016-03-11 15:07:56 -05:00
f75f77cec7 Merge branch 'feature/ui-test' into dev
Closes #22
2016-03-11 13:21:10 -05:00
8b10138cd6 Fix tests on pre-Lollipop devices 2016-03-11 13:20:43 -05:00
2b40633110 Add more tests for settings and about 2016-03-11 12:39:59 -05:00
1102d05a61 Test unarchiving habits 2016-03-11 12:39:59 -05:00
51e8c2f111 Implement basic user interface tests 2016-03-11 12:39:59 -05:00
547e4e5f63 Use white background on pre-Lollipop, instead of ripple 2016-03-11 12:39:41 -05:00
84d5c2aac6 Remove longClick hack 2016-03-11 12:26:56 -05:00
2b3b423fa3 Show color button even for a single habit 2016-03-11 12:26:34 -05:00
3b28c37c5e Rebuild order after commit 2016-03-11 12:26:13 -05:00
e749e787ad Add Japanese translation 2016-03-10 06:20:42 -05:00
9c5d582f24 Update copyright notices for translations 2016-03-10 06:10:25 -05:00
75a4edb61c Merge branch 'feature/frequency-view' into dev 2016-03-10 05:52:45 -05:00
34c0758308 Remove debug code 2016-03-10 05:49:34 -05:00
85963ae061 Add frequency widget 2016-03-10 05:43:56 -05:00
e3390d5397 Rename header to frequency; update translations 2016-03-10 05:25:42 -05:00
59a2f31a73 Fix timezone issues; rename class to HabitFrequencyView 2016-03-10 05:25:22 -05:00
c01f8450d3 Update README.md 2016-03-09 14:00:48 -05:00
cea5241135 Implement weekday frequency view 2016-03-09 08:23:55 -05:00
7784fc5c75 Add broadcast receiver to ShowHabitActivity 2016-03-08 22:13:47 -05:00
6dd017f33e Refresh also main activity when history editor closes 2016-03-08 21:57:42 -05:00
c8cd9f85f6 Remove hardcoded string 2016-03-08 21:50:42 -05:00
d038bdb741 Update widgets after history editor closes 2016-03-08 21:50:10 -05:00
f55e8d2c85 Merge branch 'feature/history-editor' into dev
Closes #14
2016-03-08 21:33:27 -05:00
f8dc1d9eae Force rebuild of scores 2016-03-08 21:30:09 -05:00
85393b0d40 Handle configuration changes 2016-03-08 21:22:59 -05:00
75599ad20c Fix timezone issues 2016-03-08 20:58:29 -05:00
c6b948cbf5 Save changes on configuration change
Fixes #16
2016-03-08 18:16:34 -05:00
4d42133a4b Add links to F-Droid 2016-03-08 08:38:16 -05:00
5b151805ff Make HistoryView not editable by default 2016-03-08 07:56:40 -05:00
aa86826bdb Refresh data after closing history editor 2016-03-08 07:53:24 -05:00
821373a340 Make history editor functional 2016-03-08 07:35:55 -05:00
8f37e293b1 Implement dummy history editor; add edit history button 2016-03-08 06:58:34 -05:00
0fb8ed0b53 Add French translation 2016-03-07 20:53:37 -05:00
0c696b2eb2 Include Material Design Icons 2016-03-07 16:41:38 -05:00
cd127bc3f8 Add copyright notices 2016-03-07 16:35:34 -05:00
2cfc809490 Update copyright notices in all files 2016-03-07 15:54:56 -05:00
ba31dee16a Merge branch 'feature/refactoring' into dev 2016-03-07 08:39:11 -05:00
146c743fb8 Simplify list adapter 2016-03-07 08:03:30 -05:00
0c00e9ec2d Simplify constructor 2016-03-07 07:51:48 -05:00
49af55a2de Move more methods to helper 2016-03-07 07:31:06 -05:00
0114b48197 Fix formatting; include license section 2016-03-07 05:44:02 -05:00
09f615a5e6 Update translations 2016-03-07 05:39:15 -05:00
9014acc548 Remove settings menu from ShowHabitActivity 2016-03-07 05:39:15 -05:00
4fb386be86 Include building and installing instructions
Closes #11
2016-03-07 05:29:05 -05:00
ea8606be97 More details to the code contribution subsection 2016-03-06 21:58:02 -05:00
e0527dc8ff Implement about screen 2016-03-06 08:34:18 -05:00
aaf2789a21 Include contributing section 2016-03-06 06:00:12 -05:00
f8dc64cc6b Move time and color pickers resources into separate file 2016-03-05 16:13:48 -05:00
ced5b751be Move methods to helper 2016-03-05 08:43:09 -05:00
8a60dda74e Further simplify ListHabitsFragment 2016-03-05 08:33:17 -05:00
c8c4df6ef7 Split ListHabitsFragment into smaller classes 2016-03-05 07:30:04 -05:00
0c0ac9dee5 Minor formatting 2016-03-05 06:48:25 -05:00
fdf6c91929 Use equals instead of operator 2016-03-05 06:48:01 -05:00
08d6e39a17 Throw exception when trying to undo deletion of habit 2016-03-05 06:46:52 -05:00
b9bc7bd1b5 Merge branch 'master' into dev 2016-03-04 13:28:39 -05:00
7b73238448 Add explicit READ_EXTERNAL_STORAGE permission with maxSdkVersion 2016-03-04 13:19:03 -05:00
83ccfb82ac Merge branch 'release/1.2.0' into master 2016-03-04 12:53:24 -05:00
199d35d9c7 Merge branch 'release/1.2.0' into dev 2016-03-04 12:53:04 -05:00
382a2fe600 Update CHANGELOG.md 2016-03-04 12:46:49 -05:00
e02f9c1d60 Bump version to 1.2.0 2016-03-04 12:46:36 -05:00
5e7636d7ff Fix position for new habits 2016-03-04 12:43:55 -05:00
616322cd35 Fix card background (pre-Lollipop) 2016-03-04 12:43:55 -05:00
299c6a0c1d Show action icons on pre-Lollipop 2016-03-04 12:43:55 -05:00
b4911b6cb4 Save last app version on preferences 2016-03-04 11:13:20 -05:00
f41f877107 Fix data export on older devices 2016-03-04 11:13:20 -05:00
58aa7f6687 Add padding to HabitScoreView 2016-03-04 07:03:05 -05:00
d196e01da0 Update widgets and reminders on background; faster startup 2016-03-04 06:52:31 -05:00
1fbd12a947 Fix incorrect streaks 2016-03-04 06:39:30 -05:00
7493291ade Use average of scores in the interval 2016-03-03 07:38:50 -05:00
2a750704d9 Minor string change 2016-03-03 07:38:11 -05:00
eb79113e4c Update screenshots to include widgets 2016-03-03 06:49:46 -05:00
cb2f3823cd Update widget previews 2016-03-03 06:36:06 -05:00
39e29dabb8 Add code to save widget preview to file 2016-03-03 06:35:37 -05:00
51d1b93d03 Split Habit class into several smaller classes 2016-03-03 05:22:19 -05:00
8acbc63914 Move commands to their own files 2016-03-03 04:42:40 -05:00
ac8e78ff24 Minor style changes 2016-03-02 10:39:13 -05:00
162ded66d8 Improve widget measuring 2016-03-02 09:52:32 -05:00
5428209543 Improve widget colors 2016-03-01 08:37:57 -05:00
141fd30d70 Merge branch 'widgets' into dev 2016-02-29 07:45:41 -05:00
48d446a243 Minor color changes 2016-02-29 07:44:59 -05:00
ae7869d3a2 Implement multiple widget providers 2016-02-29 07:19:43 -05:00
b8cacaffa9 Refactor custom views; fix rendering issues 2016-02-29 05:50:27 -05:00
4def8f0409 Perform additional checks to avoid negative lengths 2016-02-28 15:23:20 -05:00
f0d12e9925 Widgets for HistoryView, ScoreView, etc 2016-02-28 13:55:39 -05:00
a2331260e4 Alternative design for widgets 2016-02-28 11:37:50 -05:00
c1a846d42b Minor style changes 2016-02-27 19:42:05 -05:00
031d684b3e Update main activity on notification/widget click 2016-02-27 18:26:25 -05:00
3a770e71e3 Add configuration activity for widgets 2016-02-27 18:06:57 -05:00
b29dd8ea79 Remove debug code 2016-02-27 16:31:48 -05:00
7f1553a4a1 Toggle checkmarks from widget 2016-02-27 14:09:02 -05:00
d748f5d6de Assign habits to widgets; refresh on database change 2016-02-27 13:54:24 -05:00
7234e072e6 Implement widget with fixed data 2016-02-27 13:24:01 -05:00
7f71f46367 Remove useless widget preview 2016-02-27 13:12:12 -05:00
c1dae021bf Implement dummy widget 2016-02-27 11:52:46 -05:00
88455acc76 Fix check button for previous day reminders 2016-02-27 05:44:14 -05:00
6a1cb09ca2 Remove unused imports and variables 2016-02-26 08:25:56 -05:00
33d7ab52ca Remove unused resources 2016-02-26 08:11:43 -05:00
d2682358c2 Allow custom views to be rendered on the layout editor 2016-02-26 07:44:34 -05:00
f511ca2028 Explicitly allow backups 2016-02-26 05:31:26 -05:00
d5774e8511 Close cursors 2016-02-26 05:31:17 -05:00
b6e7e72f5a Remove object allocations during draw 2016-02-26 05:29:02 -05:00
27220c9ab2 Specify locale explicitly 2016-02-26 05:17:33 -05:00
f1424e5820 Add content description for images 2016-02-26 05:16:58 -05:00
a18e0fbda0 Remove hardcoded string 2016-02-26 05:09:56 -05:00
4c3a72df81 Delete duplicate resource 2016-02-26 05:08:35 -05:00
e9ce50f686 Use correct XML namespace 2016-02-26 05:07:04 -05:00
acb26964f3 Mark as untranslatable 2016-02-26 05:06:39 -05:00
917d1218ae Update pt translation 2016-02-26 05:06:18 -05:00
f5ccd7d8c3 Move ripple backgrounds to drawable-v21 2016-02-26 04:53:19 -05:00
456a9e49a9 Remove extra translations 2016-02-26 04:52:55 -05:00
6b05004647 Implement fling and more natural scrolling on ScrollableDataView 2016-02-25 21:33:19 -05:00
f9a9339042 Use GestureDetector for scrolling 2016-02-25 21:31:28 -05:00
5e21d877c5 Fetch all the data with one call 2016-02-25 20:17:44 -05:00
af0ef90e4d Export to CSV 2016-02-25 15:46:39 -05:00
02b3ea58cf Update drag-sort-listview 2016-02-25 15:46:39 -05:00
0dcedb3c59 Update README.md 2016-02-25 10:20:25 -05:00
637d75fd2e Update README.md 2016-02-25 10:17:01 -05:00
0cd8dd973b Bump version to 1.1.1 2016-02-24 18:30:29 -05:00
2898a21157 Disallow empty list of days 2016-02-24 15:58:58 -05:00
0fa25c6701 Internationalize string 2016-02-24 15:58:45 -05:00
843f3b06a1 Update translations 2016-02-24 15:58:12 -05:00
271 changed files with 17681 additions and 3206 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ build/
*.iml *.iml
art/ art/
*.actual.png

65
CHANGELOG.md Normal file
View File

@@ -0,0 +1,65 @@
# Changelog
### 1.4.0 (April 4, 2016)
* Ability to import data from third-party apps
* Ability to save and restore full database backup
* Show more information on streak chart
* Simplify interface for creating habits
* Add link to Frequently Asked Questions (FAQ)
* Reduce app loading time and lag on widgets
* Generate bug reports on crash and from settings screen
* Disable vibration according to phone settings
* Add Czech translation
* Fix wrong month names for some languages
### 1.3.3 (March 20, 2016)
* Add Spanish and Korean translations
* Make small corrections to other translations
* Fix incorrect date in history calendar
### 1.3.2 (March 18, 2016)
* Add Arabic, Italian, Polish, Russian and Swedish translations
* Minor fixes to German and French translations
* Minor bug fixes
### 1.3.1 (March 15, 2016)
* Fixes crash on devices with large screen, such as the Nexus 10
* Fixes crash when clicking widgets and reminders of deleted habits
* Other minor bug fixes
### 1.3.0 (March 12, 2016)
* New frequency plot: view total repetitions per day of week
* New history editor: put checkmarks in the past
* Add German, French and Japanese translations
* Add about screen, with credits to all contributors
* Fix small bug that prevented habit from being reordered
* Fix small bug caused by rotating the device
### 1.2.0 (March 4, 2016)
* Ability to export habit data as CSV
* Widgets (checkmark, history, score and streaks)
* More natural scrolling on data views (fling)
* Minor UI improvements on pre-Lollipop devices
* Fix crash on Samsung Galaxy TabS 8.4
* Other minor bug fixes
### 1.1.1 (February 24, 2016)
* Show reminder only on chosen days of the week
* Rearrange habits by long-pressing then dragging
* Select and modify multiple habits simultaneously
* 12/24 hour format according to phone preferences
* Permanently delete habits
* Usage hints during startup
* Translation to Brazilian Portuguese and Chinese
* Other minor fixes
### 1.0.0 (February 19, 2016)
* Initial release

110
NOTICE.md Normal file
View File

@@ -0,0 +1,110 @@
# Copyright Notices
### ActiveAndroid
<https://github.com/pardom/ActiveAndroid>
Copyright (C) 2010 Michael Pardo
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### Android Open Source Project
<https://source.android.com/>
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### FontAwesome
<http://fontawesome.io>
Font Awesome is a full suite of 605 pictographic icons for easy scalable
vector graphics on websites, created and maintained by Dave Gandy. Licensed
under the SIL OFL 1.1.
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
### DragSortListView
<https://github.com/bauerca/drag-sort-listview>
A subclass of the Android ListView component that enables drag
and drop re-ordering of list items.
Copyright 2012 Carl Bauer
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
### Material Design Icons
<https://github.com/google/material-design-icons>
Material design icons are the official icon set from Google that are designed
under the material design guidelines. Available under the Creative Common
Attribution 4.0 International License (CC-BY 4.0).
### Android Flow Layout
<https://github.com/ApmeM/android-flowlayout>
Extended linear layout that wrap its content when there is no place in the current line.
Copyright 2011, Artem Votincev (apmem.org)
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.

105
README.md
View File

@@ -1,16 +1,51 @@
# Loop Habit Tracker # Loop Habit Tracker
<a href="https://circleci.com/gh/iSoron/uhabits/tree/dev">
<img src="https://img.shields.io/circleci/project/iSoron/uhabits/dev.svg">
</a>
<a href="https://codecov.io/github/iSoron/uhabits?branch=dev">
<img src="https://img.shields.io/codecov/c/github/iSoron/uhabits.svg" alt="Coverage via Codecov" />
</a>
Loop is a simple Android app that helps you create and maintain good habits. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source, with no intrusive permissions. Join the open beta at [Google Play Store](https://play.google.com/apps/testing/org.isoron.uhabits). Loop is a simple Android app that helps you create and maintain good habits,
allowing you to achieve your long-term goals. Detailed graphs and statistics
show you how your habits improved over time. It is completely ad-free and open
source.
<p align="center">
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p>
## Features ## Features
* Simple and beautiful interface, following the Material Design guidelines. * **Simple, beautiful and modern interface.** Loop has a minimalistic interface
* Advanced algorithms for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress. that is easy to use and follows the material design guidelines.
* Detailed graphs and statistics, showing how did you habits improve over time. Scroll back to see the complete history of your habit.
* Support for both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day. * **Habit score.** In addition to showing your current streak, Loop has an
* Habit reminders at a chosen hour of the day. advanced algorithm for calculating the strength of your habits. Every
* Support for Android Wear. Reminders can be checked or dismissed from the watch. repetition makes your habit stronger, and every missed day makes it weaker. A
* Completely ad-free and open source. There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under the GPLv3. few missed days after a long streak, however, will not completely destroy
your entire progress.
* **Detailed graphs and statistics.** Clearly see how your habits improved over
time with beautiful and detailed graphs. Scroll back to see the complete
history of your habits.
* **Flexible schedules.** Supports both daily habits and habits with more
complex schedules, such as 3 times every week; one time every other week; or
every other day.
* **Reminders.** Create an individual reminder for each habit, at a chosen hour
of the day. Easily check, dismiss or snooze your habit directly from the
notification, without opening the app.
* **Optimized for smartwatches.** Reminders can be checked, snoozed or
dismissed directly from your Android Wear watch.
* **Completely ad-free and open source.** There are absolutely no
advertisements, annoying notifications or intrusive permissions in this app,
and there will never be. The complete source code is available under the
GPLv3.
## Screenshots ## Screenshots
@@ -18,12 +53,66 @@ Loop is a simple Android app that helps you create and maintain good habits. Det
[![Edit habit][screen2th]][screen2] [![Edit habit][screen2th]][screen2]
[![Habit strength][screen3th]][screen3] [![Habit strength][screen3th]][screen3]
[![Habit history and streaks][screen4th]][screen4] [![Habit history and streaks][screen4th]][screen4]
[![Widgets][screen5th]][screen5]
## Installing
The easiest way to install Loop is through the [Google Play Store][playstore] or [F-Droid][fdroid].
You may also download and install the APK from the [releases page][releases];
note, however, that the app will not be updated automatically. To build this
app from the source code, see [building instructions][build].
## Contributing
Loop is an open source project developed entirely by volunteers. If you would
like to contribute to the project, you are very welcome. There are many ways to
contribute, even if you are not a software developer.
* **Report bugs, suggest features.** The easiest way to contribute is to simply
use the app and let us know if you find any problems or have any suggestions
to improve it. You can either use the link inside the app, or open an issue
at GitHub.
* **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
other users find it more easily.
* **Translate the app into your own language.** If you are not a native English
speaker, and would like to see the app translated into your own language,
please join our [open translation project at POEditor][poedit]. If the translation
is already completed, you are also very welcome to join and proofread it.
* **Write some code.** If you are an Android developer, you are very welcome to
contribute with code. Please, see the [developer guidelines][dev-guide] for more details.
## License
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
[screen1]: screenshots/original/uhabits1.png [screen1]: screenshots/original/uhabits1.png
[screen2]: screenshots/original/uhabits2.png [screen2]: screenshots/original/uhabits2.png
[screen3]: screenshots/original/uhabits3.png [screen3]: screenshots/original/uhabits3.png
[screen4]: screenshots/original/uhabits4.png [screen4]: screenshots/original/uhabits4.png
[screen5]: screenshots/original/uhabits5.png
[screen1th]: screenshots/thumbs/uhabits1.png [screen1th]: screenshots/thumbs/uhabits1.png
[screen2th]: screenshots/thumbs/uhabits2.png [screen2th]: screenshots/thumbs/uhabits2.png
[screen3th]: screenshots/thumbs/uhabits3.png [screen3th]: screenshots/thumbs/uhabits3.png
[screen4th]: screenshots/thumbs/uhabits4.png [screen4th]: screenshots/thumbs/uhabits4.png
[screen5th]: screenshots/thumbs/uhabits5.png
[poedit]: https://poeditor.com/join/project/8DWX5pfjS0
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
[releases]: https://github.com/iSoron/uhabits/releases
[fdroid]: http://f-droid.org/app/org.isoron.uhabits
[dev-guide]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines
[build]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines#building

View File

@@ -2,12 +2,18 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion "21.1.2" buildToolsVersion "23.0.1"
defaultConfig { defaultConfig {
applicationId "org.isoron.uhabits" applicationId "org.isoron.uhabits"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 23 targetSdkVersion 23
buildConfigField "Integer", "databaseVersion", "13"
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//testInstrumentationRunnerArgument "size", "small"
} }
buildTypes { buildTypes {
@@ -15,6 +21,9 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
} }
debug {
testCoverageEnabled = true
}
} }
lintOptions { lintOptions {
@@ -25,7 +34,25 @@ android {
dependencies { dependencies {
compile 'com.android.support:support-v4:23.1.1' compile 'com.android.support:support-v4:23.1.1'
compile 'com.github.paolorotolo:appintro:3.4.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 project(':libs:drag-sort-listview:library')
compile files('libs/ActiveAndroid.jar') compile files('libs/ActiveAndroid.jar')
androidTestCompile 'com.android.support:support-annotations:23.1.1'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
} }
task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') {
commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ')
}
tasks.whenTaskAdded { task ->
if (task.name.startsWith('connected')) {
task.dependsOn grantAnimationPermission
}
}

View File

@@ -0,0 +1,19 @@
HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText
Breed dragons,with love and fire,Diet & Food,2016-03-18,1,
Breed dragons,with love and fire,Diet & Food,2016-03-19,1,
Breed dragons,with love and fire,Diet & Food,2016-03-21,1,
Reduce sleep,only 2 hours per day,Time Management,2016-03-15,1,
Reduce sleep,only 2 hours per day,Time Management,2016-03-16,1,
Reduce sleep,only 2 hours per day,Time Management,2016-03-17,1,
Reduce sleep,only 2 hours per day,Time Management,2016-03-21,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-15,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-16,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-18,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-21,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-15,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-16,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-18,1,
No-arms pushup,Become like water my friend!,Fitness,2016-03-21,1,
Grow spiritually,"transcend ego, practice compassion, smile and breath",Meditation,2016-03-15,1,
Grow spiritually,"transcend ego, practice compassion, smile and breath",Meditation,2016-03-17,1,
Grow spiritually,"transcend ego, practice compassion, smile and breath",Meditation,2016-03-21,1,
1 HabitName HabitDescription HabitCategory CalendarDate Value CommentText
2 Breed dragons with love and fire Diet & Food 2016-03-18 1
3 Breed dragons with love and fire Diet & Food 2016-03-19 1
4 Breed dragons with love and fire Diet & Food 2016-03-21 1
5 Reduce sleep only 2 hours per day Time Management 2016-03-15 1
6 Reduce sleep only 2 hours per day Time Management 2016-03-16 1
7 Reduce sleep only 2 hours per day Time Management 2016-03-17 1
8 Reduce sleep only 2 hours per day Time Management 2016-03-21 1
9 No-arms pushup Become like water my friend! Fitness 2016-03-15 1
10 No-arms pushup Become like water my friend! Fitness 2016-03-16 1
11 No-arms pushup Become like water my friend! Fitness 2016-03-18 1
12 No-arms pushup Become like water my friend! Fitness 2016-03-21 1
13 No-arms pushup Become like water my friend! Fitness 2016-03-15 1
14 No-arms pushup Become like water my friend! Fitness 2016-03-16 1
15 No-arms pushup Become like water my friend! Fitness 2016-03-18 1
16 No-arms pushup Become like water my friend! Fitness 2016-03-21 1
17 Grow spiritually transcend ego, practice compassion, smile and breath Meditation 2016-03-15 1
18 Grow spiritually transcend ego, practice compassion, smile and breath Meditation 2016-03-17 1
19 Grow spiritually transcend ego, practice compassion, smile and breath Meditation 2016-03-21 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

View File

@@ -0,0 +1,5 @@
#!/bin/bash
P=/sdcard/Android/data/org.isoron.uhabits/cache/Failed/
adb pull $P Failed/
adb shell rm -r $P

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.tasks.BaseTask;
import org.junit.Before;
import java.util.concurrent.TimeoutException;
public class BaseTest
{
protected Context testContext;
protected Context targetContext;
private static boolean isLooperPrepared;
public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC)
@Before
public void setup()
{
if(!isLooperPrepared)
{
Looper.prepare();
isLooperPrepared = true;
}
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
}
protected void waitForAsyncTasks() throws InterruptedException, TimeoutException
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
{
Thread.sleep(1000);
return;
}
BaseTask.waitForTasks(10000);
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.ui;
import android.preference.Preference;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.isoron.uhabits.models.Habit;
public class HabitMatchers
{
public static Matcher<Habit> withName(final String name)
{
return new TypeSafeMatcher<Habit>()
{
@Override
public boolean matchesSafely(Habit habit)
{
return habit.name.equals(name);
}
@Override
public void describeTo(Description description)
{
description.appendText("name should be ").appendText(name);
}
@Override
public void describeMismatchSafely(Habit habit, Description description)
{
description.appendText("was ").appendText(habit.name);
}
};
}
public static Matcher<View> containsHabit(final Matcher<Habit> matcher)
{
return new TypeSafeMatcher<View>()
{
@Override
protected boolean matchesSafely(View view)
{
Adapter adapter = ((AdapterView) view).getAdapter();
for (int i = 0; i < adapter.getCount(); i++)
if (matcher.matches(adapter.getItem(i))) return true;
return false;
}
@Override
public void describeTo(Description description)
{
description.appendText("with class name: ");
matcher.describeTo(description);
}
};
}
public static Matcher<?> isPreferenceWithText(final String text)
{
return (Matcher<?>) new BaseMatcher()
{
@Override
public boolean matches(Object o)
{
if(!(o instanceof Preference)) return false;
return o.toString().contains(text);
}
@Override
public void describeTo(Description description)
{
description.appendText(String.format("is preference with text '%s'", text));
}
};
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.ui;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralClickAction;
import android.support.test.espresso.action.GeneralLocation;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Tap;
import android.support.test.espresso.matcher.ViewMatchers;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.hamcrest.Matcher;
import org.isoron.uhabits.R;
import java.security.InvalidParameterException;
import java.util.Random;
public class HabitViewActions
{
public static ViewAction toggleAllCheckmarks()
{
final GeneralClickAction clickAction =
new GeneralClickAction(Tap.LONG, GeneralLocation.CENTER, Press.FINGER);
return new ViewAction()
{
@Override
public Matcher<View> getConstraints()
{
return ViewMatchers.isDisplayed();
}
@Override
public String getDescription()
{
return "toggleAllCheckmarks";
}
@Override
public void perform(UiController uiController, View view)
{
if (view.getId() != R.id.llButtons)
throw new InvalidParameterException("View must have id llButtons");
LinearLayout llButtons = (LinearLayout) view;
int count = llButtons.getChildCount();
for (int i = 0; i < count; i++)
{
TextView tvButton = (TextView) llButtons.getChildAt(i);
clickAction.perform(uiController, tvButton);
}
}
};
}
public static ViewAction clickAt(final int x, final int y)
{
return new GeneralClickAction(Tap.SINGLE, new CoordinatesProvider()
{
@Override
public float[] calculateCoordinates(View view)
{
int[] locations = new int[2];
view.getLocationOnScreen(locations);
final float locationX = locations[0] + x;
final float locationY = locations[1] + y;
return new float[]{locationX, locationY};
}
}, Press.FINGER);
}
public static ViewAction clickAtRandomLocations(final int count)
{
return new ViewAction()
{
@Override
public Matcher<View> getConstraints()
{
return ViewMatchers.isDisplayed();
}
@Override
public String getDescription()
{
return "clickAtRandomLocations";
}
@Override
public void perform(UiController uiController, View view)
{
int width = view.getWidth();
int height = view.getHeight();
Random random = new Random();
for(int i = 0; i < count; i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
ViewAction action = clickAt(x, y);
action.perform(uiController, view);
}
}
};
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.ui;
import android.support.test.espresso.NoMatchingViewException;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openContextualActionModeOverflowMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.isoron.uhabits.ui.HabitMatchers.containsHabit;
import static org.isoron.uhabits.ui.HabitMatchers.withName;
public class MainActivityActions
{
public static String addHabit()
{
return addHabit(false);
}
public static String addHabit(boolean openDialogs)
{
String name = "New Habit " + new Random().nextInt(1000000);
String description = "Did you perform your new habit today?";
String num = "4";
String den = "8";
onView(withId(R.id.action_add))
.perform(click());
typeHabitData(name, description, num, den);
if(openDialogs)
{
onView(withId(R.id.buttonPickColor))
.perform(click());
pressBack();
onView(withId(R.id.inputReminderTime))
.perform(click());
onView(withText("Done"))
.perform(click());
onView(withId(R.id.inputReminderDays))
.perform(click());
onView(withText("OK"))
.perform(click());
}
onView(withId(R.id.buttonSave))
.perform(click());
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label));
return name;
}
public static void typeHabitData(String name, String description, String num, String den)
{
onView(withId(R.id.input_name))
.perform(replaceText(name));
onView(withId(R.id.input_description))
.perform(replaceText(description));
try
{
onView(allOf(withId(R.id.sFrequency), withEffectiveVisibility(VISIBLE)))
.perform(click());
onData(allOf(instanceOf(String.class), startsWith("Custom")))
.inRoot(isPlatformPopup())
.perform(click());
}
catch(NoMatchingViewException e)
{
// ignored
}
onView(withId(R.id.input_freq_num))
.perform(replaceText(num));
onView(withId(R.id.input_freq_den))
.perform(replaceText(den));
}
public static void selectHabit(String name)
{
selectHabits(Collections.singletonList(name));
}
public static void selectHabits(List<String> names)
{
boolean first = true;
for(String name : names)
{
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(first ? longClick() : click());
first = false;
}
}
public static void assertHabitsDontExist(List<String> names)
{
for(String name : names)
onView(withId(R.id.listView))
.check(matches(not(containsHabit(withName(name)))));
}
public static void assertHabitExists(String name)
{
List<String> names = new LinkedList<>();
names.add(name);
assertHabitsExist(names);
}
public static void assertHabitsExist(List<String> names)
{
for(String name : names)
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.check(matches(isDisplayed()));
}
public static void deleteHabit(String name)
{
deleteHabits(Collections.singletonList(name));
}
public static void deleteHabits(List<String> names)
{
selectHabits(names);
clickActionModeMenuItem(R.string.delete);
onView(withText("OK"))
.perform(click());
assertHabitsDontExist(names);
}
public static void clickActionModeMenuItem(int stringId)
{
try
{
onView(withText(stringId)).perform(click());
}
catch (Exception e1)
{
try
{
onView(withContentDescription(stringId)).perform(click());
}
catch(Exception e2)
{
openContextualActionModeOverflowMenu();
onView(withText(stringId)).perform(click());
}
}
}
}

View File

@@ -0,0 +1,359 @@
/*
* 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.ui;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.longClick;
import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.action.ViewActions.swipeUp;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.Intents.intending;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
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;
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;
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.deleteHabit;
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
import static org.isoron.uhabits.ui.MainActivityActions.selectHabits;
import static org.isoron.uhabits.ui.MainActivityActions.typeHabitData;
import static org.isoron.uhabits.ui.ShowHabitActivityActions.openHistoryEditor;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainTest
{
private SystemHelper sys;
@Rule
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(
MainActivity.class);
private Context targetContext;
@Before
public void setup()
{
Context context = InstrumentationRegistry.getInstrumentation().getContext();
sys = new SystemHelper(context);
sys.disableAllAnimations();
sys.acquireWakeLock();
sys.unlockScreen();
targetContext = InstrumentationRegistry.getTargetContext();
Instrumentation.ActivityResult okResult = new Instrumentation.ActivityResult(
Activity.RESULT_OK, new Intent());
intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult);
intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(okResult);
intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult);
skipTutorial();
}
@After
public void tearDown()
{
sys.releaseWakeLock();
}
public void skipTutorial()
{
try
{
for (int i = 0; i < 10; i++)
onView(allOf(withClassName(endsWith("AppCompatImageButton")),
isDisplayed())).perform(click());
}
catch (NoMatchingViewException e)
{
// ignored
}
}
/**
* User opens the app, creates some habits, selects them, archives them, select 'show archived'
* on the menu, selects the previously archived habits and then deletes them.
*/
@Test
public void testArchiveHabits()
{
List<String> names = new LinkedList<>();
for(int i = 0; i < 3; i++)
names.add(addHabit());
selectHabits(names);
clickActionModeMenuItem(R.string.archive);
assertHabitsDontExist(names);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.show_archived))
.perform(click());
assertHabitsExist(names);
selectHabits(names);
clickActionModeMenuItem(R.string.unarchive);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.show_archived))
.perform(click());
assertHabitsExist(names);
deleteHabits(names);
}
/**
* User opens the app, clicks the add button, types some bogus information, tries to save,
* dialog displays an error.
*/
@Test
public void testAddInvalidHabit()
{
onView(withId(R.id.action_add))
.perform(click());
typeHabitData("", "", "15", "7");
onView(withId(R.id.buttonSave)).perform(click());
onView(withId(R.id.input_name)).check(matches(isDisplayed()));
}
/**
* User creates a habit, toggles a bunch of checkmarks, clicks the habit to open the statistics
* screen, scrolls down to some views, then scrolls the views backwards and forwards in time.
*/
@Test
public void testAddHabitAndViewStats() throws InterruptedException
{
String name = addHabit(true);
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.llButtons))
.perform(toggleAllCheckmarks());
Thread.sleep(1200);
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
onView(withId(R.id.scoreView))
.perform(scrollTo(), swipeRight());
onView(withId(R.id.punchcardView))
.perform(scrollTo(), swipeRight());
}
/**
* User creates a habit, selects the habit, clicks edit button, changes some information about
* the habit, click save button, sees changes on the main window, selects habit again,
* changes color, then deletes the habit.
*/
@Test
public void testEditHabit()
{
String name = addHabit();
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(longClick());
clickActionModeMenuItem(R.string.edit);
String modifiedName = "Modified " + new Random().nextInt(10000);
typeHabitData(modifiedName, "", "1", "1");
onView(withId(R.id.buttonSave))
.perform(click());
assertHabitExists(modifiedName);
selectHabit(modifiedName);
clickActionModeMenuItem(R.string.color_picker_default_title);
pressBack();
deleteHabit(modifiedName);
}
/**
* User creates a habit, opens statistics page, clicks button to edit history, adds some
* checkmarks, closes dialog, sees the modified history calendar.
*/
@Test
public void testEditHistory()
{
String name = addHabit();
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
openHistoryEditor();
onView(withClassName(endsWith("HabitHistoryView")))
.perform(clickAtRandomLocations(20));
pressBack();
onView(withId(R.id.historyView))
.perform(scrollTo(), swipeRight(), swipeLeft());
}
/**
* User opens menu, clicks settings, sees settings screen.
*/
@Test
public void testSettings()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
}
/**
* User opens menu, clicks about, sees about screen.
*/
@Test
public void testAbout()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.about)).perform(click());
onView(isRoot()).perform(swipeUp());
}
/**
* User opens menu, clicks Help, sees website.
*/
@Test
public void testHelp()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.help)).perform(click());
intended(hasAction(Intent.ACTION_VIEW));
}
/**
* User creates a habit, exports full backup, deletes the habit, restores backup, sees that the
* previously created habit has appeared back.
*/
@Test
public void testExportImportDB()
{
String name = addHabit();
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
date = date.substring(0, date.length() - 2);
onData(isPreferenceWithText("Export full backup")).perform(click());
intended(hasAction(Intent.ACTION_SEND));
deleteHabit(name);
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Import data")).perform(click());
onData(allOf(is(instanceOf(String.class)), startsWith("Backups")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), containsString(date)))
.perform(click());
selectHabit(name);
}
/**
* User creates a habit, opens settings, clicks export as CSV, is asked what activity should
* handle the file.
*/
@Test
public void testExportCSV()
{
addHabit();
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Export as CSV")).perform(click());
intended(hasAction(Intent.ACTION_SEND));
}
/**
* User opens the settings and generates a bug report.
*/
@Test
public void testGenerateBugReport()
{
openActionBarOverflowOrOptionsMenu(targetContext);
onView(withText(R.string.settings)).perform(click());
onData(isPreferenceWithText("Generate bug report")).perform(click());
intended(hasAction(Intent.ACTION_SENDTO));
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.ui;
import android.support.test.espresso.matcher.ViewMatchers;
import org.isoron.uhabits.R;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.scrollTo;
public class ShowHabitActivityActions
{
public static void openHistoryEditor()
{
onView(ViewMatchers.withId(R.id.btEditHistory))
.perform(scrollTo(), click());
}
}

View File

@@ -0,0 +1,105 @@
package org.isoron.uhabits.ui;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.test.runner.AndroidJUnitRunner;
import android.util.Log;
import java.lang.reflect.Method;
public final class SystemHelper extends AndroidJUnitRunner
{
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
private static final float DISABLED = 0.0f;
private static final float DEFAULT = 1.0f;
private final Context context;
private PowerManager.WakeLock wakeLock;
SystemHelper(Context context)
{
this.context = context;
}
void acquireWakeLock()
{
PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = power.newWakeLock(PowerManager.FULL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.ON_AFTER_RELEASE, getClass().getSimpleName());
wakeLock.acquire();
}
void releaseWakeLock()
{
if(wakeLock != null)
wakeLock.release();
}
void unlockScreen()
{
Log.i("SystemHelper", "Trying to unlock screen");
try
{
KeyguardManager mKeyGuardManager =
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
KeyguardManager.KeyguardLock mLock = mKeyGuardManager.newKeyguardLock("lock");
mLock.disableKeyguard();
Log.e("SystemHelper", "Successfully unlocked screen");
} catch (Exception e)
{
Log.e("SystemHelper", "Could not unlock screen");
e.printStackTrace();
}
}
void disableAllAnimations()
{
Log.i("SystemHelper", "Trying to disable animations");
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED) setSystemAnimationsScale(DISABLED);
else Log.e("SystemHelper", "Permission denied");
}
void enableAllAnimations()
{
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED)
{
setSystemAnimationsScale(DEFAULT);
}
}
private void setSystemAnimationsScale(float animationScale)
{
try
{
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
Method asInterface =
windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
Method setAnimationScales =
windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
for (int i = 0; i < currentScales.length; i++)
currentScales[i] = animationScale;
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
Log.i("SystemHelper", "All animations successfully disabled");
}
catch (Exception e)
{
Log.e("SystemHelper", "Could not change animation scale to " + animationScale + " :'(");
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit;
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;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.tasks.ExportDBTask;
import org.isoron.uhabits.tasks.ImportDataTask;
import java.io.File;
import java.io.InputStream;
import java.util.Random;
import static org.junit.Assert.fail;
public class HabitFixtures
{
public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false,
false, true, true };
public static Habit createShortHabit()
{
Habit habit = new Habit();
habit.name = "Wake up early";
habit.description = "Did you wake up before 6am?";
habit.freqNum = 2;
habit.freqDen = 3;
habit.save();
long timestamp = DateHelper.getStartOfToday();
for(boolean c : NON_DAILY_HABIT_CHECKS)
{
if(c) habit.repetitions.toggle(timestamp);
timestamp -= DateHelper.millisecondsInOneDay;
}
return habit;
}
public static Habit createEmptyHabit()
{
Habit habit = new Habit();
habit.name = "Meditate";
habit.description = "Did you meditate this morning?";
habit.color = ColorHelper.palette[3];
habit.freqNum = 1;
habit.freqDen = 1;
habit.save();
return habit;
}
public static Habit createLongHabit()
{
Habit habit = createEmptyHabit();
habit.freqNum = 3;
habit.freqDen = 7;
habit.color = ColorHelper.palette[4];
habit.save();
long day = DateHelper.millisecondsInOneDay;
long today = DateHelper.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52,
53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95,
102, 103, 108, 109, 120};
for(int mark : marks)
habit.repetitions.toggle(today - mark * day);
return habit;
}
public static void generateHugeDataSet() throws Throwable
{
final int nHabits = 30;
final int nYears = 5;
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
{
@Override
public void execute()
{
Random rand = new Random();
for(int i = 0; i < nHabits; i++)
{
Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits));
Habit habit = new Habit();
habit.name = String.format("Habit %d", i);
habit.save();
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
for(int j = 0; j < 365 * nYears; j++)
{
if(rand.nextBoolean())
habit.repetitions.toggle(today - j * day);
}
habit.scores.getTodayValue();
habit.streaks.getAll(1);
}
}
});
ExportDBTask task = new ExportDBTask(null);
task.setListener(new ExportDBTask.Listener()
{
@Override
public void onExportDBFinished(@Nullable String filename)
{
if(filename != null)
Log.i("HabitFixture", String.format("Huge data set exported to %s", filename));
else
Log.i("HabitFixture", "Failed to save database");
}
});
task.execute();
BaseTask.waitForTasks(30000);
}
public static void loadHugeDataSet(Context testContext) throws Throwable
{
File baseDir = DatabaseHelper.getFilesDir("Backups");
if(baseDir == null) fail("baseDir should not be null");
File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db"));
InputStream in = testContext.getAssets().open("fixtures/loopHuge.db");
DatabaseHelper.copy(in, dst);
ImportDataTask task = new ImportDataTask(dst, null);
task.execute();
BaseTask.waitForTasks(30000);
}
public static void purgeHabits()
{
for(Habit h : Habit.getAll(true))
h.cascadeDelete();
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.unit;
import android.os.Build;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.HabitsApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitsApplicationTest
{
@Test
public void test_getLogcat() throws IOException
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
return;
String msg = "LOGCAT TEST";
new RuntimeException(msg).printStackTrace();
String log = HabitsApplication.getLogcat();
assertThat(log, containsString(msg));
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ArchiveHabitsCommandTest extends BaseTest
{
private ArchiveHabitsCommand command;
private Habit habit;
@Before
public void setup()
{
super.setup();
habit = HabitFixtures.createShortHabit();
command = new ArchiveHabitsCommand(Collections.singletonList(habit));
}
@Test
public void testExecuteUndoRedo()
{
assertFalse(habit.isArchived());
command.execute();
assertTrue(habit.isArchived());
command.undo();
assertFalse(habit.isArchived());
command.execute();
assertTrue(habit.isArchived());
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
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;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ChangeHabitColorCommandTest extends BaseTest
{
private ChangeHabitColorCommand command;
private LinkedList<Habit> habits;
@Before
public void setup()
{
super.setup();
habits = new LinkedList<>();
for(int i = 0; i < 3; i ++)
{
Habit habit = HabitFixtures.createShortHabit();
habit.color = ColorHelper.palette[i+1];
habit.save();
habits.add(habit);
}
command = new ChangeHabitColorCommand(habits, ColorHelper.palette[0]);
}
@Test
public void testExecuteUndoRedo()
{
checkOriginalColors();
command.execute();
checkNewColors();
command.undo();
checkOriginalColors();
command.execute();
checkNewColors();
}
private void checkOriginalColors()
{
int k = 0;
for(Habit h : habits)
assertThat(h.color, equalTo(ColorHelper.palette[++k]));
}
private void checkNewColors()
{
for(Habit h : habits)
assertThat(h.color, equalTo(ColorHelper.palette[0]));
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.CreateHabitCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CreateHabitCommandTest extends BaseTest
{
private CreateHabitCommand command;
private Habit model;
@Before
public void setup()
{
super.setup();
model = new Habit();
model.name = "New habit";
command = new CreateHabitCommand(model);
HabitFixtures.purgeHabits();
}
@Test
public void testExecuteUndoRedo()
{
assertTrue(Habit.getAll(true).isEmpty());
command.execute();
List<Habit> allHabits = Habit.getAll(true);
assertThat(allHabits.size(), equalTo(1));
Habit habit = allHabits.get(0);
Long id = habit.getId();
assertThat(habit.name, equalTo(model.name));
command.undo();
assertTrue(Habit.getAll(true).isEmpty());
command.execute();
allHabits = Habit.getAll(true);
assertThat(allHabits.size(), equalTo(1));
habit = allHabits.get(0);
Long newId = habit.getId();
assertThat(id, equalTo(newId));
assertThat(habit.name, equalTo(model.name));
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.DeleteHabitsCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DeleteHabitsCommandTest extends BaseTest
{
private DeleteHabitsCommand command;
private LinkedList<Habit> habits;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habits = new LinkedList<>();
// Habits that shuold be deleted
for(int i = 0; i < 3; i ++)
{
Habit habit = HabitFixtures.createShortHabit();
habits.add(habit);
}
// Extra habit that should not be deleted
Habit extraHabit = HabitFixtures.createShortHabit();
extraHabit.name = "extra";
extraHabit.save();
command = new DeleteHabitsCommand(habits);
}
@Test
public void testExecuteUndoRedo()
{
assertThat(Habit.getAll(true).size(), equalTo(4));
command.execute();
assertThat(Habit.getAll(true).size(), equalTo(1));
assertThat(Habit.getAll(true).get(0).name, equalTo("extra"));
thrown.expect(UnsupportedOperationException.class);
command.undo();
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.EditHabitCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class EditHabitCommandTest extends BaseTest
{
private EditHabitCommand command;
private Habit habit;
private Habit modified;
private Long id;
@Before
public void setup()
{
super.setup();
habit = HabitFixtures.createShortHabit();
habit.name = "original";
habit.freqDen = 1;
habit.freqNum = 1;
habit.save();
id = habit.getId();
modified = new Habit(habit);
modified.name = "modified";
}
@Test
public void testExecuteUndoRedo()
{
command = new EditHabitCommand(habit, modified);
int originalScore = habit.scores.getTodayValue();
assertThat(habit.name, equalTo("original"));
command.execute();
refreshHabit();
assertThat(habit.name, equalTo("modified"));
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
command.undo();
refreshHabit();
assertThat(habit.name, equalTo("original"));
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
command.execute();
refreshHabit();
assertThat(habit.name, equalTo("modified"));
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
}
@Test
public void testExecuteUndoRedo_withModifiedInterval()
{
modified.freqNum = 1;
modified.freqDen = 7;
command = new EditHabitCommand(habit, modified);
int originalScore = habit.scores.getTodayValue();
assertThat(habit.name, equalTo("original"));
command.execute();
refreshHabit();
assertThat(habit.name, equalTo("modified"));
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
command.undo();
refreshHabit();
assertThat(habit.name, equalTo("original"));
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
command.execute();
refreshHabit();
assertThat(habit.name, equalTo("modified"));
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
}
private void refreshHabit()
{
habit = Habit.get(id);
assertTrue(habit != null);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ToggleRepetitionCommandTest extends BaseTest
{
private ToggleRepetitionCommand command;
private Habit habit;
private long today;
@Before
public void setup()
{
super.setup();
habit = HabitFixtures.createShortHabit();
today = DateHelper.getStartOfToday();
command = new ToggleRepetitionCommand(habit, today);
}
@Test
public void testExecuteUndoRedo()
{
assertTrue(habit.repetitions.contains(today));
command.execute();
assertFalse(habit.repetitions.contains(today));
command.undo();
assertTrue(habit.repetitions.contains(today));
command.execute();
assertFalse(habit.repetitions.contains(today));
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.unit.commands;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UnarchiveHabitsCommandTest extends BaseTest
{
private UnarchiveHabitsCommand command;
private Habit habit;
@Before
public void setup()
{
super.setup();
habit = HabitFixtures.createShortHabit();
Habit.archive(Collections.singletonList(habit));
command = new UnarchiveHabitsCommand(Collections.singletonList(habit));
}
@Test
public void testExecuteUndoRedo()
{
assertTrue(habit.isArchived());
command.execute();
assertFalse(habit.isArchived());
command.undo();
assertTrue(habit.isArchived());
command.execute();
assertFalse(habit.isArchived());
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.unit.io;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.io.HabitsCSVExporter;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitsCSVExporterTest extends BaseTest
{
private File baseDir;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
HabitFixtures.createShortHabit();
HabitFixtures.createEmptyHabit();
Context targetContext = InstrumentationRegistry.getTargetContext();
baseDir = targetContext.getCacheDir();
}
private void unzip(File file) throws IOException
{
ZipFile zip = new ZipFile(file);
Enumeration<? extends ZipEntry> e = zip.entries();
while(e.hasMoreElements())
{
ZipEntry entry = e.nextElement();
InputStream stream = zip.getInputStream(entry);
String outputFilename = String.format("%s/%s", baseDir.getAbsolutePath(),
entry.getName());
File outputFile = new File(outputFilename);
File parent = outputFile.getParentFile();
if(parent != null) parent.mkdirs();
DatabaseHelper.copy(stream, outputFile);
}
zip.close();
}
@Test
public void testExportCSV() throws IOException
{
List<Habit> habits = Habit.getAll(true);
HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir);
String filename = exporter.writeArchive();
assertAbsolutePathExists(filename);
File archive = new File(filename);
unzip(archive);
assertPathExists("Habits.csv");
assertPathExists("001 Wake up early");
assertPathExists("001 Wake up early/Checkmarks.csv");
assertPathExists("001 Wake up early/Scores.csv");
assertPathExists("002 Meditate/Checkmarks.csv");
assertPathExists("002 Meditate/Scores.csv");
}
private void assertPathExists(String s)
{
assertAbsolutePathExists(String.format("%s/%s", baseDir.getAbsolutePath(), s));
}
private void assertAbsolutePathExists(String s)
{
File file = new File(s);
assertTrue(String.format("File %s should exist", file.getAbsolutePath()), file.exists());
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.unit.io;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.io.GenericImporter;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.GregorianCalendar;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ImportTest extends BaseTest
{
private File baseDir;
private Context context;
@Before
public void setup()
{
super.setup();
DateHelper.setFixedLocalTime(null);
HabitFixtures.purgeHabits();
context = InstrumentationRegistry.getInstrumentation().getContext();
baseDir = DatabaseHelper.getFilesDir("Backups");
if(baseDir == null) fail("baseDir should not be null");
}
private void copyAssetToFile(String assetPath, File dst) throws IOException
{
InputStream in = context.getAssets().open(assetPath);
DatabaseHelper.copy(in, dst);
}
private void importFromFile(String assetFilename) throws IOException
{
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
copyAssetToFile(assetFilename, file);
assertTrue(file.exists());
assertTrue(file.canRead());
GenericImporter importer = new GenericImporter();
assertThat(importer.canHandle(file), is(true));
importer.importHabitsFromFile(file);
}
private boolean containsRepetition(Habit h, int year, int month, int day)
{
GregorianCalendar date = DateHelper.getStartOfTodayCalendar();
date.set(year, month - 1, day);
return h.repetitions.contains(date.getTimeInMillis());
}
@Test
public void testTickmateDB() throws IOException
{
importFromFile("tickmate.db");
List<Habit> habits = Habit.getAll(true);
assertThat(habits.size(), equalTo(3));
Habit h = habits.get(0);
assertThat(h.name, equalTo("Vegan"));
assertTrue(containsRepetition(h, 2016, 1, 24));
assertTrue(containsRepetition(h, 2016, 2, 5));
assertTrue(containsRepetition(h, 2016, 3, 18));
assertFalse(containsRepetition(h, 2016, 3, 14));
}
@Test
public void testRewireDB() throws IOException
{
importFromFile("rewire.db");
List<Habit> habits = Habit.getAll(true);
assertThat(habits.size(), equalTo(3));
Habit habit = habits.get(0);
assertThat(habit.name, equalTo("Wake up early"));
assertThat(habit.freqNum, equalTo(3));
assertThat(habit.freqDen, equalTo(7));
assertFalse(habit.hasReminder());
assertFalse(containsRepetition(habit, 2015, 12, 31));
assertTrue(containsRepetition(habit, 2016, 1, 18));
assertTrue(containsRepetition(habit, 2016, 1, 28));
assertFalse(containsRepetition(habit, 2016, 3, 10));
habit = habits.get(1);
assertThat(habit.name, equalTo("brush teeth"));
assertThat(habit.freqNum, equalTo(3));
assertThat(habit.freqDen, equalTo(7));
assertThat(habit.reminderHour, equalTo(8));
assertThat(habit.reminderMin, equalTo(0));
boolean[] reminderDays = {false, true, true, true, true, true, false};
assertThat(habit.reminderDays, equalTo(DateHelper.packWeekdayList(reminderDays)));
}
@Test
public void testHabitBullCSV() throws IOException
{
importFromFile("habitbull.csv");
List<Habit> habits = Habit.getAll(true);
assertThat(habits.size(), equalTo(4));
Habit habit = habits.get(0);
assertThat(habit.name, equalTo("Breed dragons"));
assertThat(habit.description, equalTo("with love and fire"));
assertThat(habit.freqNum, equalTo(1));
assertThat(habit.freqDen, equalTo(1));
assertTrue(containsRepetition(habit, 2016, 3, 18));
assertTrue(containsRepetition(habit, 2016, 3, 19));
assertFalse(containsRepetition(habit, 2016, 3, 20));
}
@Test
public void testLoopDB() throws IOException
{
importFromFile("loop.db");
List<Habit> habits = Habit.getAll(true);
assertThat(habits.size(), equalTo(9));
Habit habit = habits.get(0);
assertThat(habit.name, equalTo("Wake up early"));
assertThat(habit.freqNum, equalTo(3));
assertThat(habit.freqDen, equalTo(7));
assertTrue(containsRepetition(habit, 2016, 3, 14));
assertTrue(containsRepetition(habit, 2016, 3, 16));
assertFalse(containsRepetition(habit, 2016, 3, 17));
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.unit.models;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.StringWriter;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY;
import static org.isoron.uhabits.models.Checkmark.UNCHECKED;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CheckmarkListTest extends BaseTest
{
Habit nonDailyHabit;
private Habit emptyHabit;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
nonDailyHabit = HabitFixtures.createShortHabit();
emptyHabit = HabitFixtures.createEmptyHabit();
}
@After
public void tearDown()
{
DateHelper.setFixedLocalTime(null);
}
@Test
public void test_getAllValues_withNonDailyHabit()
{
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED,
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getAllValues_withEmptyHabit()
{
int[] expectedValues = new int[0];
int[] actualValues = emptyHabit.checkmarks.getAllValues();
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getAllValues_moveForwardInTime()
{
travelInTime(3);
int[] expectedValues = { UNCHECKED, UNCHECKED, UNCHECKED, CHECKED_EXPLICITLY, UNCHECKED,
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getAllValues_moveBackwardsInTime()
{
travelInTime(-3);
int[] expectedValues = { CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getValues_withInvalidInterval()
{
int values[] = nonDailyHabit.checkmarks.getValues(100L, -100L);
assertThat(values, equalTo(new int[0]));
}
@Test
public void test_getValues_withValidInterval()
{
long from = DateHelper.getStartOfToday() - 15 * DateHelper.millisecondsInOneDay;
long to = DateHelper.getStartOfToday() - 5 * DateHelper.millisecondsInOneDay;
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, UNCHECKED, UNCHECKED, UNCHECKED,
UNCHECKED, UNCHECKED };
int[] actualValues = nonDailyHabit.checkmarks.getValues(from, to);
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getTodayValue()
{
travelInTime(-1);
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
travelInTime(0);
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
travelInTime(1);
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
}
@Test
public void test_writeCSV() throws IOException
{
String expectedCSV =
"2015-01-16,2\n" +
"2015-01-17,2\n" +
"2015-01-18,1\n" +
"2015-01-19,0\n" +
"2015-01-20,2\n" +
"2015-01-21,2\n" +
"2015-01-22,2\n" +
"2015-01-23,1\n" +
"2015-01-24,0\n" +
"2015-01-25,2\n";
StringWriter writer = new StringWriter();
nonDailyHabit.checkmarks.writeCSV(writer);
assertThat(writer.toString(), equalTo(expectedCSV));
}
private void travelInTime(int days)
{
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME +
days * DateHelper.millisecondsInOneDay);
}
}

View File

@@ -0,0 +1,379 @@
/*
* 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.unit.models;
import android.graphics.Color;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.hamcrest.MatcherAssert;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitTest extends BaseTest
{
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
}
@Test
public void testConstructor_default()
{
Habit habit = new Habit();
assertThat(habit.archived, is(0));
assertThat(habit.highlight, is(0));
assertThat(habit.reminderHour, is(nullValue()));
assertThat(habit.reminderMin, is(nullValue()));
assertThat(habit.reminderDays, is(not(nullValue())));
assertThat(habit.streaks, is(not(nullValue())));
assertThat(habit.scores, is(not(nullValue())));
assertThat(habit.repetitions, is(not(nullValue())));
assertThat(habit.checkmarks, is(not(nullValue())));
}
@Test
public void testConstructor_habit()
{
Habit model = new Habit();
model.archived = 1;
model.highlight = 1;
model.color = Color.BLACK;
model.freqNum = 10;
model.freqDen = 20;
model.reminderDays = 1;
model.reminderHour = 8;
model.reminderMin = 30;
model.position = 0;
Habit habit = new Habit(model);
assertThat(habit.archived, is(model.archived));
assertThat(habit.highlight, is(model.highlight));
assertThat(habit.color, is(model.color));
assertThat(habit.freqNum, is(model.freqNum));
assertThat(habit.freqDen, is(model.freqDen));
assertThat(habit.reminderDays, is(model.reminderDays));
assertThat(habit.reminderHour, is(model.reminderHour));
assertThat(habit.reminderMin, is(model.reminderMin));
assertThat(habit.position, is(model.position));
}
@Test
public void test_get_withValidId()
{
Habit habit = new Habit();
habit.save();
Habit habit2 = Habit.get(habit.getId());
assertThat(habit, equalTo(habit2));
}
@Test
public void test_get_withInvalidId()
{
Habit habit = Habit.get(123456L);
assertThat(habit, is(nullValue()));
}
@Test
public void test_getAll_withoutArchived()
{
List<Habit> habits = new LinkedList<>();
List<Habit> habitsWithArchived = new LinkedList<>();
for(int i = 0; i < 10; i++)
{
Habit h = new Habit();
if(i % 2 == 0)
h.archived = 1;
else
habits.add(h);
habitsWithArchived.add(h);
h.save();
}
assertThat(habits, equalTo(Habit.getAll(false)));
assertThat(habitsWithArchived, equalTo(Habit.getAll(true)));
}
@Test
public void test_getByPosition()
{
List<Habit> habits = new LinkedList<>();
for(int i = 0; i < 10; i++)
{
Habit h = new Habit();
h.save();
habits.add(h);
}
for(int i = 0; i < 10; i++)
{
Habit h = Habit.getByPosition(i);
if(h == null) fail();
assertThat(h, equalTo(habits.get(i)));
}
}
@Test
public void test_count()
{
for(int i = 0; i < 10; i++)
{
Habit h = new Habit();
if(i % 2 == 0) h.archived = 1;
h.save();
}
assertThat(Habit.count(), equalTo(5));
}
@Test
public void test_countWithArchived()
{
for(int i = 0; i < 10; i++)
{
Habit h = new Habit();
if(i % 2 == 0) h.archived = 1;
h.save();
}
assertThat(Habit.countWithArchived(), equalTo(10));
}
@Test
public void test_updateId()
{
Habit habit = new Habit();
habit.name = "Hello World";
habit.save();
Long oldId = habit.getId();
Long newId = 123456L;
Habit.updateId(oldId, newId);
Habit newHabit = Habit.get(newId);
if(newHabit == null) fail();
assertThat(newHabit, is(not(nullValue())));
assertThat(newHabit.name, equalTo(habit.name));
}
@Test
public void test_reorder()
{
List<Long> ids = new LinkedList<>();
int n = 10;
for (int i = 0; i < n; i++)
{
Habit h = new Habit();
h.save();
ids.add(h.getId());
assertThat(h.position, is(i));
}
int operations[][] = {
{5, 2},
{3, 7},
{4, 4},
{3, 2}
};
int expectedPosition[][] = {
{0, 1, 3, 4, 5, 2, 6, 7, 8, 9},
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
{0, 1, 7, 2, 4, 3, 5, 6, 8, 9},
};
for(int i = 0; i < operations.length; i++)
{
int from = operations[i][0];
int to = operations[i][1];
Habit fromHabit = Habit.getByPosition(from);
Habit toHabit = Habit.getByPosition(to);
Habit.reorder(fromHabit, toHabit);
int actualPositions[] = new int[n];
for (int j = 0; j < n; j++)
{
Habit h = Habit.get(ids.get(j));
if (h == null) fail();
actualPositions[j] = h.position;
}
assertThat(actualPositions, equalTo(expectedPosition[i]));
}
}
@Test
public void test_rebuildOrder()
{
List<Long> ids = new LinkedList<>();
int originalPositions[] = { 0, 1, 1, 4, 6, 8, 10, 10, 13};
for (int p : originalPositions)
{
Habit h = new Habit();
h.position = p;
h.save();
ids.add(h.getId());
}
Habit.rebuildOrder();
for (int i = 0; i < originalPositions.length; i++)
{
Habit h = Habit.get(ids.get(i));
if(h == null) fail();
assertThat(h.position, is(i));
}
}
@Test
public void test_getHabitsWithReminder()
{
List<Habit> habitsWithReminder = new LinkedList<>();
for(int i = 0; i < 10; i++)
{
Habit habit = new Habit();
if(i % 2 == 0)
{
habit.reminderDays = DateHelper.ALL_WEEK_DAYS;
habit.reminderHour = 8;
habit.reminderMin = 30;
habitsWithReminder.add(habit);
}
habit.save();
}
assertThat(habitsWithReminder, equalTo(Habit.getHabitsWithReminder()));
}
@Test
public void test_archive_unarchive()
{
List<Habit> allHabits = new LinkedList<>();
List<Habit> archivedHabits = new LinkedList<>();
List<Habit> unarchivedHabits = new LinkedList<>();
for(int i = 0; i < 10; i++)
{
Habit habit = new Habit();
habit.save();
allHabits.add(habit);
if(i % 2 == 0)
archivedHabits.add(habit);
else
unarchivedHabits.add(habit);
}
Habit.archive(archivedHabits);
assertThat(Habit.getAll(false), equalTo(unarchivedHabits));
assertThat(Habit.getAll(true), equalTo(allHabits));
Habit.unarchive(archivedHabits);
assertThat(Habit.getAll(false), equalTo(allHabits));
assertThat(Habit.getAll(true), equalTo(allHabits));
}
@Test
public void test_setColor()
{
List<Habit> habits = new LinkedList<>();
for(int i = 0; i < 10; i++)
{
Habit habit = new Habit();
habit.color = i;
habit.save();
habits.add(habit);
}
int newColor = 100;
Habit.setColor(habits, newColor);
for(Habit h : habits)
assertThat(h.color, equalTo(newColor));
}
@Test
public void test_hasReminder_clearReminder()
{
Habit h = new Habit();
assertThat(h.hasReminder(), is(false));
h.reminderDays = DateHelper.ALL_WEEK_DAYS;
h.reminderHour = 8;
h.reminderMin = 30;
assertThat(h.hasReminder(), is(true));
h.clearReminder();
assertThat(h.hasReminder(), is(false));
}
@Test
public void test_writeCSV() throws IOException
{
HabitFixtures.createEmptyHabit();
HabitFixtures.createShortHabit();
String expectedCSV =
"Name,Description,NumRepetitions,Interval,Color\n" +
"Meditate,Did you meditate this morning?,1,1,#AFB42B\n" +
"Wake up early,Did you wake up before 6am?,2,3,#00897B\n";
StringWriter writer = new StringWriter();
Habit.writeCSV(Habit.getAll(true), writer);
MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV));
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.unit.models;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Repetition;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Random;
import static junit.framework.Assert.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RepetitionListTest extends BaseTest
{
private Habit habit;
private Habit emptyHabit;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habit = HabitFixtures.createShortHabit();
emptyHabit = HabitFixtures.createEmptyHabit();
}
@After
public void tearDown()
{
DateHelper.setFixedLocalTime(null);
}
@Test
public void test_contains()
{
long current = DateHelper.getStartOfToday();
for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS)
{
assertThat(habit.repetitions.contains(current), equalTo(b));
current -= DateHelper.millisecondsInOneDay;
}
for(int i = 0; i < 3; i++)
{
assertThat(habit.repetitions.contains(current), equalTo(false));
current -= DateHelper.millisecondsInOneDay;
}
}
@Test
public void test_delete()
{
long timestamp = DateHelper.getStartOfToday();
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
habit.repetitions.delete(timestamp);
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
}
@Test
public void test_toggle()
{
long timestamp = DateHelper.getStartOfToday();
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
habit.repetitions.toggle(timestamp);
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
habit.repetitions.toggle(timestamp);
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
}
@Test
public void test_getWeekDayFrequency()
{
Random random = new Random();
Integer weekdayCount[][] = new Integer[12][7];
Integer monthCount[] = new Integer[12];
Arrays.fill(monthCount, 0);
for(Integer row[] : weekdayCount)
Arrays.fill(row, 0);
GregorianCalendar day = DateHelper.getStartOfTodayCalendar();
// Sets the current date to the end of November
day.set(2015, 10, 30);
DateHelper.setFixedLocalTime(day.getTimeInMillis());
// Add repetitions randomly from January to December
// Leaves the month of March empty, to check that it returns null
day.set(2015, 0, 1);
for(int i = 0; i < 365; i ++)
{
if(random.nextBoolean())
{
int month = day.get(Calendar.MONTH);
int week = day.get(Calendar.DAY_OF_WEEK) % 7;
if(month != 2)
{
if (month <= 10)
{
weekdayCount[month][week]++;
monthCount[month]++;
}
emptyHabit.repetitions.toggle(day.getTimeInMillis());
}
}
day.add(Calendar.DAY_OF_YEAR, 1);
}
HashMap<Long, Integer[]> freq = emptyHabit.repetitions.getWeekdayFrequency();
// Repetitions until November should be counted correctly
for(int month = 0; month < 11; month++)
{
day.set(2015, month, 1);
Integer actualCount[] = freq.get(day.getTimeInMillis());
if(monthCount[month] == 0)
assertThat(actualCount, equalTo(null));
else
assertThat(actualCount, equalTo(weekdayCount[month]));
}
// Repetitions in December should be discarded
day.set(2015, 11, 1);
assertThat(freq.get(day.getTimeInMillis()), equalTo(null));
}
@Test
public void test_count()
{
long to = DateHelper.getStartOfToday();
long from = to - 9 * DateHelper.millisecondsInOneDay;
assertThat(habit.repetitions.count(from, to), equalTo(6));
to = DateHelper.getStartOfToday() - DateHelper.millisecondsInOneDay;
from = to - 5 * DateHelper.millisecondsInOneDay;
assertThat(habit.repetitions.count(from, to), equalTo(3));
}
@Test
public void test_getOldest()
{
long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay;
assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp));
Repetition oldest = habit.repetitions.getOldest();
assertFalse(oldest == null);
assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp));
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.unit.models;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.StringWriter;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ScoreListTest extends BaseTest
{
private Habit habit;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habit = HabitFixtures.createEmptyHabit();
}
@After
public void tearDown()
{
DateHelper.setFixedLocalTime(null);
}
@Test
public void test_invalidateNewerThan()
{
assertThat(habit.scores.getTodayValue(), equalTo(0));
toggleRepetitions(0, 2);
assertThat(habit.scores.getTodayValue(), equalTo(1948077));
habit.freqNum = 1;
habit.freqDen = 2;
habit.scores.invalidateNewerThan(0);
assertThat(habit.scores.getTodayValue(), equalTo(1974654));
}
@Test
public void test_getTodayStarValue()
{
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.EMPTY_STAR));
int k = 0;
while(habit.scores.getTodayValue() < Score.HALF_STAR_CUTOFF) toggleRepetitions(k, ++k);
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.HALF_STAR));
while(habit.scores.getTodayValue() < Score.FULL_STAR_CUTOFF) toggleRepetitions(k, ++k);
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.FULL_STAR));
}
@Test
public void test_getTodayValue()
{
toggleRepetitions(0, 20);
assertThat(habit.scores.getTodayValue(), equalTo(12629351));
}
@Test
public void test_getValue()
{
toggleRepetitions(0, 20);
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
4507040, 3699107, 2846927, 1948077, 1000000 };
long current = DateHelper.getStartOfToday();
for(int expectedValue : expectedValues)
{
assertThat(habit.scores.getValue(current), equalTo(expectedValue));
current -= DateHelper.millisecondsInOneDay;
}
}
@Test
public void test_getAllValues_withoutGroups()
{
toggleRepetitions(0, 20);
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
4507040, 3699107, 2846927, 1948077, 1000000 };
int actualValues[] = habit.scores.getAllValues(1);
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_getAllValues_withGroups()
{
toggleRepetitions(0, 20);
int expectedValues[] = { 11434978, 7894999, 3212362 };
int actualValues[] = habit.scores.getAllValues(7);
assertThat(actualValues, equalTo(expectedValues));
}
@Test
public void test_writeCSV() throws IOException
{
HabitFixtures.purgeHabits();
Habit habit = HabitFixtures.createShortHabit();
String expectedCSV =
"2015-01-16,0.0519\n" +
"2015-01-17,0.1021\n" +
"2015-01-18,0.0986\n" +
"2015-01-19,0.0952\n" +
"2015-01-20,0.1439\n" +
"2015-01-21,0.1909\n" +
"2015-01-22,0.2364\n" +
"2015-01-23,0.2283\n" +
"2015-01-24,0.2205\n" +
"2015-01-25,0.2649\n";
StringWriter writer = new StringWriter();
habit.scores.writeCSV(writer);
assertThat(writer.toString(), equalTo(expectedCSV));
}
private void toggleRepetitions(final int from, final int to)
{
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
{
@Override
public void execute()
{
long today = DateHelper.getStartOfToday();
for (int i = from; i < to; i++)
habit.repetitions.toggle(today - i * DateHelper.millisecondsInOneDay);
}
});
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.unit.models;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Score;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ScoreTest extends BaseTest
{
@Before
public void setup()
{
super.setup();
}
@Test
public void test_compute_withDailyHabit()
{
int checkmark = Checkmark.UNCHECKED;
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
checkmark = Checkmark.CHECKED_IMPLICITLY;
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
checkmark = Checkmark.CHECKED_EXPLICITLY;
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
}
@Test
public void test_compute_withNonDailyHabit()
{
int checkmark = Checkmark.CHECKED_EXPLICITLY;
assertThat(Score.compute(1/3.0, 0, checkmark), equalTo(1000000));
assertThat(Score.compute(1/3.0, 5000000, checkmark), equalTo(5916180));
assertThat(Score.compute(1/3.0, 10000000, checkmark), equalTo(10832360));
assertThat(Score.compute(1/3.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
assertThat(Score.compute(1/7.0, 0, checkmark), equalTo(1000000));
assertThat(Score.compute(1/7.0, 5000000, checkmark), equalTo(5964398));
assertThat(Score.compute(1/7.0, 10000000, checkmark), equalTo(10928796));
assertThat(Score.compute(1/7.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
}
@Test
public void test_getStarStatus()
{
Score s = new Score();
s.score = Score.FULL_STAR_CUTOFF + 1;
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
s.score = Score.FULL_STAR_CUTOFF;
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
s.score = Score.FULL_STAR_CUTOFF - 1;
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
s.score = Score.HALF_STAR_CUTOFF + 1;
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
s.score = Score.HALF_STAR_CUTOFF;
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
s.score = Score.HALF_STAR_CUTOFF - 1;
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
s.score = 0;
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.unit.tasks;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.ExportCSVTask;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.List;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ExportCSVTaskTest extends BaseTest
{
@Before
public void setup()
{
super.setup();
}
@Test
public void testExportCSV() throws Throwable
{
HabitFixtures.createShortHabit();
List<Habit> habits = Habit.getAll(true);
ProgressBar bar = new ProgressBar(targetContext);
ExportCSVTask task = new ExportCSVTask(habits, bar);
task.setListener(new ExportCSVTask.Listener()
{
@Override
public void onExportCSVFinished(String archiveFilename)
{
assertThat(archiveFilename, is(not(nullValue())));
File f = new File(archiveFilename);
assertTrue(f.exists());
assertTrue(f.canRead());
}
});
task.execute();
waitForAsyncTasks();
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.unit.tasks;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.tasks.ExportDBTask;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ExportDBTaskTest extends BaseTest
{
@Before
public void setup()
{
super.setup();
}
@Test
public void testExportCSV() throws Throwable
{
Context context = InstrumentationRegistry.getContext();
ProgressBar bar = new ProgressBar(context);
ExportDBTask task = new ExportDBTask(bar);
task.setListener(new ExportDBTask.Listener()
{
@Override
public void onExportDBFinished(String filename)
{
assertThat(filename, is(not(nullValue())));
File f = new File(filename);
assertTrue(f.exists());
assertTrue(f.canRead());
}
});
task.execute();
waitForAsyncTasks();
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.unit.tasks;
import android.support.annotation.NonNull;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.tasks.ImportDataTask;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ImportDataTaskTest extends BaseTest
{
private File baseDir;
@Before
public void setup()
{
super.setup();
baseDir = DatabaseHelper.getFilesDir("Backups");
if(baseDir == null) fail("baseDir should not be null");
}
private void copyAssetToFile(String assetPath, File dst) throws IOException
{
InputStream in = testContext.getAssets().open(assetPath);
DatabaseHelper.copy(in, dst);
}
private void assertTaskResult(final int expectedResult, String assetFilename) throws Throwable
{
ImportDataTask task = createTask(assetFilename);
task.setListener(new ImportDataTask.Listener()
{
@Override
public void onImportFinished(int result)
{
assertThat(result, equalTo(expectedResult));
}
});
task.execute();
waitForAsyncTasks();
}
@NonNull
private ImportDataTask createTask(String assetFilename) throws IOException
{
ProgressBar bar = new ProgressBar(targetContext);
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
copyAssetToFile(assetFilename, file);
return new ImportDataTask(file, bar);
}
@Test
public void testImportInvalidData() throws Throwable
{
assertTaskResult(ImportDataTask.NOT_RECOGNIZED, "icon.png");
}
@Test
public void testImportValidData() throws Throwable
{
assertTaskResult(ImportDataTask.SUCCESS, "loop.db");
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.CheckmarkView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CheckmarkViewTest extends ViewTest
{
private CheckmarkView view;
private Habit habit;
@Before
public void setup()
{
super.setup();
habit = HabitFixtures.createShortHabit();
view = new CheckmarkView(targetContext);
view.setHabit(habit);
refreshData(view);
measureView(dpToPixels(100), dpToPixels(200), view);
}
@Test
public void testRender_checked() throws IOException
{
assertRenders(view, "CheckmarkView/checked.png");
}
@Test
public void testRender_unchecked() throws IOException
{
habit.repetitions.toggle(DateHelper.getStartOfToday());
view.refreshData();
assertRenders(view, "CheckmarkView/unchecked.png");
}
@Test
public void testRender_implicitlyChecked() throws IOException
{
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
habit.repetitions.toggle(today);
habit.repetitions.toggle(today - day);
habit.repetitions.toggle(today - 2 * day);
view.refreshData();
assertRenders(view, "CheckmarkView/implicitly_checked.png");
}
@Test
public void testRender_largeSize() throws IOException
{
measureView(dpToPixels(300), dpToPixels(300), view);
assertRenders(view, "CheckmarkView/large_size.png");
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitFrequencyView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitFrequencyViewTest extends ViewTest
{
private HabitFrequencyView view;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
Habit habit = HabitFixtures.createLongHabit();
view = new HabitFrequencyView(targetContext);
view.setHabit(habit);
refreshData(view);
measureView(dpToPixels(300), dpToPixels(100), view);
}
@Test
public void testRender() throws Throwable
{
assertRenders(view, "HabitFrequencyView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitFrequencyView/renderTransparent.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "HabitFrequencyView/renderDifferentSize.png");
}
@Test
public void testRender_withDataOffset() throws Throwable
{
view.onScroll(null, null, -dpToPixels(150), 0);
view.invalidate();
assertRenders(view, "HabitFrequencyView/renderDataOffset.png");
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitHistoryView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitHistoryViewTest extends ViewTest
{
private Habit habit;
private HabitHistoryView view;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habit = HabitFixtures.createLongHabit();
view = new HabitHistoryView(targetContext);
view.setHabit(habit);
measureView(dpToPixels(300), dpToPixels(100), view);
refreshData(view);
}
@Test
public void testRender() throws Throwable
{
assertRenders(view, "HabitHistoryView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitHistoryView/renderTransparent.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "HabitHistoryView/renderDifferentSize.png");
}
@Test
public void testRender_withDataOffset() throws Throwable
{
view.onScroll(null, null, -dpToPixels(150), 0);
view.invalidate();
assertRenders(view, "HabitHistoryView/renderDataOffset.png");
}
@Test
public void tapDate_withEditableView() throws Throwable
{
view.setIsEditable(true);
tap(view, 270, 30);
waitForAsyncTasks();
long today = DateHelper.getStartOfToday();
assertFalse(habit.repetitions.contains(today));
}
@Test
public void tapDate_atInvalidLocations() throws Throwable
{
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
waitForAsyncTasks();
int actualCheckmarkValues[] = habit.checkmarks.getAllValues();
assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues));
}
@Test
public void tapDate_withReadOnlyView() throws Throwable
{
view.setIsEditable(false);
tap(view, 270, 30);
waitForAsyncTasks();
long today = DateHelper.getStartOfToday();
assertTrue(habit.repetitions.contains(today));
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitScoreView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitScoreViewTest extends ViewTest
{
private Habit habit;
private HabitScoreView view;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
habit = HabitFixtures.createLongHabit();
view = new HabitScoreView(targetContext);
view.setHabit(habit);
view.setBucketSize(7);
refreshData(view);
measureView(dpToPixels(300), dpToPixels(100), view);
}
@Test
public void testRender() throws Throwable
{
Log.d("HabitScoreViewTest", String.format("height=%d", dpToPixels(100)));
assertRenders(view, "HabitScoreView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitScoreView/renderTransparent.png");
}
@Test
public void testRender_withDifferentSize() throws Throwable
{
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "HabitScoreView/renderDifferentSize.png");
}
@Test
public void testRender_withDataOffset() throws Throwable
{
view.onScroll(null, null, -dpToPixels(150), 0);
view.invalidate();
assertRenders(view, "HabitScoreView/renderDataOffset.png");
}
@Test
public void testRender_withMonthlyBucket() throws Throwable
{
view.setBucketSize(30);
view.refreshData();
view.invalidate();
assertRenders(view, "HabitScoreView/renderMonthly.png");
}
@Test
public void testRender_withYearlyBucket() throws Throwable
{
view.setBucketSize(365);
view.refreshData();
view.invalidate();
assertRenders(view, "HabitScoreView/renderYearly.png");
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.HabitStreakView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class HabitStreakViewTest extends ViewTest
{
private HabitStreakView view;
@Before
public void setup()
{
super.setup();
HabitFixtures.purgeHabits();
Habit habit = HabitFixtures.createLongHabit();
view = new HabitStreakView(targetContext);
measureView(dpToPixels(300), dpToPixels(100), view);
view.setHabit(habit);
refreshData(view);
}
@Test
public void testRender() throws Throwable
{
assertRenders(view, "HabitStreakView/render.png");
}
@Test
public void testRender_withTransparentBackground() throws Throwable
{
view.setIsBackgroundTransparent(true);
assertRenders(view, "HabitStreakView/renderTransparent.png");
}
@Test
public void testRender_withSmallSize() throws Throwable
{
measureView(dpToPixels(100), dpToPixels(100), view);
refreshData(view);
assertRenders(view, "HabitStreakView/renderSmallSize.png");
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.views.NumberView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NumberViewTest extends ViewTest
{
private NumberView view;
@Before
public void setup()
{
super.setup();
view = new NumberView(targetContext);
view.setLabel("Hello world");
view.setNumber(31);
view.setColor(ColorHelper.palette[0]);
measureView(dpToPixels(100), dpToPixels(100), view);
}
@Test
public void testRender_base() throws IOException
{
assertRenders(view, "NumberView/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, "NumberView/renderLongLabel.png");
}
@Test
public void testRender_withDifferentParams() throws IOException
{
view.setNumber(500);
view.setColor(ColorHelper.palette[5]);
view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize));
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "NumberView/renderDifferentParams.png");
}
}

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.unit.views;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.views.RingView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RingViewTest extends ViewTest
{
private RingView view;
@Before
public void setup()
{
super.setup();
view = new RingView(targetContext);
view.setLabel("Hello world");
view.setPercentage(0.6f);
view.setColor(ColorHelper.palette[0]);
view.setMaxDiameter(dpToPixels(100));
}
@Test
public void testRender_base() throws IOException
{
measureView(dpToPixels(100), dpToPixels(100), view);
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]);
measureView(dpToPixels(200), dpToPixels(200), view);
assertRenders(view, "RingView/renderDifferentParams.png");
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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.unit.views;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.SystemClock;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.views.HabitDataView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static junit.framework.Assert.fail;
public class ViewTest extends BaseTest
{
protected static final double SIMILARITY_CUTOFF = 0.09;
public static final int HISTOGRAM_BIN_SIZE = 8;
protected void measureView(int width, int height, View view)
{
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
protected void assertRenders(View view, String expectedImagePath) throws IOException
{
StringBuilder errorMessage = new StringBuilder();
expectedImagePath = getVersionedViewAssetPath(expectedImagePath);
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap actual = view.getDrawingCache();
Bitmap expected = getBitmapFromAssets(expectedImagePath);
int width = actual.getWidth();
int height = actual.getHeight();
Bitmap scaledExpected = Bitmap.createScaledBitmap(expected, width, height, true);
double distance;
boolean similarEnough = true;
if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF)
{
similarEnough = false;
errorMessage.append(String.format(
"Rendered image has wrong histogram (distance=%f). ",
distance));
}
if(!similarEnough)
{
saveBitmap(expectedImagePath, ".scaledExpected", scaledExpected);
String path = saveBitmap(expectedImagePath, ".actual", actual);
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
fail(errorMessage.toString());
}
actual.recycle();
expected.recycle();
scaledExpected.recycle();
}
private Bitmap getBitmapFromAssets(String path) throws IOException
{
InputStream stream = testContext.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
}
private String getVersionedViewAssetPath(String path)
{
String result = null;
if (android.os.Build.VERSION.SDK_INT >= 21)
{
try
{
String vpath = "views-v21/" + path;
testContext.getAssets().open(vpath);
result = vpath;
}
catch (IOException e)
{
// ignored
}
}
if(result == null)
result = "views/" + path;
return result;
}
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
throws IOException
{
File dir = DatabaseHelper.getSDCardDir("test-screenshots");
if(dir == null) dir = DatabaseHelper.getFilesDir("test-screenshots");
if(dir == null) throw new RuntimeException("Could not find suitable dir for screenshots");
filename = filename.replaceAll("\\.png$", suffix + ".png");
String absolutePath = String.format("%s/%s", dir.getAbsolutePath(), filename);
File parent = new File(absolutePath).getParentFile();
if(!parent.exists() && !parent.mkdirs())
throw new RuntimeException(String.format("Could not create dir: %s",
parent.getAbsolutePath()));
FileOutputStream out = new FileOutputStream(absolutePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return absolutePath;
}
private int[][] getHistogram(Bitmap bitmap)
{
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
for(int x = 0; x < bitmap.getWidth(); x++)
{
for(int y = 0; y < bitmap.getHeight(); y++)
{
int color = bitmap.getPixel(x, y);
int[] argb = new int[]{
(color >> 24) & 0xff, //alpha
(color >> 16) & 0xff, //red
(color >> 8) & 0xff, //green
(color ) & 0xff //blue
};
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
}
}
return histogram;
}
private double compareHistograms(int[][] actualHistogram, int[][] expectedHistogram)
{
long diff = 0;
long total = 0;
for(int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i ++)
{
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
total += actualHistogram[0][i];
total += actualHistogram[1][i];
total += actualHistogram[2][i];
total += actualHistogram[3][i];
}
return (double) diff / total / 2;
}
protected int dpToPixels(int dp)
{
return (int) UIHelper.dpToPixels(targetContext, dp);
}
protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException
{
long now = SystemClock.uptimeMillis();
MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
protected void refreshData(final HabitDataView view)
{
new BaseTask()
{
@Override
protected void doInBackground()
{
view.refreshData();
}
}.execute();
try
{
waitForAsyncTasks();
}
catch (Exception e)
{
throw new RuntimeException("Time out");
}
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:replace="maxSdkVersion"
android:maxSdkVersion="99" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:replace="maxSdkVersion"
android:maxSdkVersion="99" />
</manifest>

View File

@@ -1,45 +1,62 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by the
~ Free Software Foundation, either version 3 of the License, or (at your
~ option) any later version.
~
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest <manifest
package="org.isoron.uhabits" package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="6" android:versionCode="14"
android:versionName="1.1.0"> android:versionName="1.4.0">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission <uses-permission
android:name="android.permission.VIBRATE"/> android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<application <application
android:name="com.activeandroid.app.Application" android:name="HabitsApplication"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title" android:label="@string/main_activity_title"
android:theme="@style/AppBaseTheme" android:theme="@style/AppBaseTheme"
android:backupAgent=".HabitsBackupAgent"> android:supportsRtl="true">
<meta-data
android:name="AA_DB_NAME"
android:value="uhabits.db"/>
<meta-data
android:name="AA_DB_VERSION"
android:value="11"/>
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw" /> android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/main_activity_title"> android:label="@string/main_activity_title">
<intent-filter <intent-filter android:label="@string/app_name">
android:label="@string/app_name">
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<receiver
android:name=".ReminderAlarmReceiver" />
<activity <activity
android:name=".ShowHabitActivity" android:name=".ShowHabitActivity"
android:label="@string/title_activity_show_habit" android:label="@string/title_activity_show_habit"
@@ -58,9 +75,87 @@
android:value="org.isoron.uhabits.MainActivity"/> android:value="org.isoron.uhabits.MainActivity"/>
</activity> </activity>
<activity android:name=".IntroActivity" <activity
android:label="" android:name=".IntroActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<activity
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:label="@string/about"
android:parentActivityName=".MainActivity">
</activity>
<receiver
android:name=".widgets.CheckmarkWidgetProvider"
android:label="@string/checkmark">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
</receiver>
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="@string/history">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="@string/habit_strength">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="@string/streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
</receiver>
<receiver
android:name=".widgets.FrequencyWidgetProvider"
android:label="@string/frequency">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
</receiver>
<receiver android:name=".HabitBroadcastReceiver"/>
</application> </application>
</manifest> </manifest>

View File

@@ -0,0 +1,3 @@
delete from Score;
delete from Streak;
delete from Checkmarks;

View File

@@ -0,0 +1,4 @@
create index idx_score_habit_timestamp on score(habit, timestamp);
create index idx_checkmark_habit_timestamp on checkmarks(habit, timestamp);
create index idx_repetitions_habit_timestamp on repetitions(habit, timestamp);
create index idx_streak_habit_end on streak(habit, end);

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -23,6 +23,7 @@ import java.util.Locale;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActionBar.LayoutParams; import android.app.ActionBar.LayoutParams;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.Context; import android.content.Context;
@@ -132,6 +133,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
// Empty constructor required for dialog fragment. // Empty constructor required for dialog fragment.
} }
@SuppressLint("Java")
public TimePickerDialog(Context context, int theme, OnTimeSetListener callback, public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) { int hourOfDay, int minute, boolean is24HourMode) {
// Empty constructor required for dialog fragment. // Empty constructor required for dialog fragment.

View File

@@ -1,34 +0,0 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.helpers;
public abstract class Command
{
public abstract void execute();
public abstract void undo();
public Integer getExecuteStringId()
{
return null;
}
public Integer getUndoStringId()
{
return null;
}
}

View File

@@ -1,68 +0,0 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.helpers;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import org.isoron.uhabits.R;
public abstract class DialogHelper
{
private static Typeface fontawesome;
public interface OnSavedListener
{
void onSaved(Command command, Object savedObject);
}
public static void showSoftKeyboard(View view)
{
InputMethodManager imm = (InputMethodManager) view.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
public static void vibrate(Context context, int duration)
{
Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(duration);
}
public static void incrementLaunchCount(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int count = prefs.getInt("launch_count", 0);
prefs.edit().putInt("launch_count", count + 1).apply();
}
public static int getLaunchCount(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getInt("launch_count", 0);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.isoron.uhabits.helpers.ColorHelper;
public class AboutActivity extends Activity implements View.OnClickListener
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
if (android.os.Build.VERSION.SDK_INT >= 21)
{
int color = getResources().getColor(R.color.blue_700);
int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f);
getActionBar().setBackgroundDrawable(new ColorDrawable(color));
getWindow().setStatusBarColor(darkerColor);
}
TextView tvVersion = (TextView) findViewById(R.id.tvVersion);
TextView tvRate = (TextView) findViewById(R.id.tvRate);
TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback);
TextView tvSource = (TextView) findViewById(R.id.tvSource);
tvVersion.setText(String.format(getResources().getString(R.string.version_n),
BuildConfig.VERSION_NAME));
tvRate.setOnClickListener(this);
tvFeedback.setOnClickListener(this);
tvSource.setOnClickListener(this);
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tvRate:
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(getString(R.string.playStoreURL)));
startActivity(intent);
break;
}
case R.id.tvFeedback:
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SENDTO);
intent.setData(Uri.parse(getString(R.string.feedbackURL)));
startActivity(intent);
break;
}
case R.id.tvSource:
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(getString(R.string.sourceCodeURL)));
startActivity(intent);
break;
}
}
}
}

View File

@@ -1,20 +1,23 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.helpers; package org.isoron.uhabits;
import android.app.Activity; import android.app.Activity;
import android.app.backup.BackupManager; import android.app.backup.BackupManager;
@@ -22,11 +25,11 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast; import android.widget.Toast;
import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command;
import java.util.LinkedList; import java.util.LinkedList;
abstract public class ReplayableActivity extends Activity abstract public class BaseActivity extends Activity implements Thread.UncaughtExceptionHandler
{ {
private static int MAX_UNDO_LEVEL = 15; private static int MAX_UNDO_LEVEL = 15;
@@ -34,11 +37,16 @@ abstract public class ReplayableActivity extends Activity
private LinkedList<Command> redoList; private LinkedList<Command> redoList;
private Toast toast; private Toast toast;
Thread.UncaughtExceptionHandler androidExceptionHandler;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
undoList = new LinkedList<>(); undoList = new LinkedList<>();
redoList = new LinkedList<>(); redoList = new LinkedList<>();
} }
@@ -100,7 +108,7 @@ abstract public class ReplayableActivity extends Activity
@Override @Override
protected void onPostExecute(Void aVoid) protected void onPostExecute(Void aVoid)
{ {
ReplayableActivity.this.onPostExecuteCommand(refreshKey); BaseActivity.this.onPostExecuteCommand(refreshKey);
BackupManager.dataChanged("org.isoron.uhabits"); BackupManager.dataChanged("org.isoron.uhabits");
} }
}.execute(); }.execute();
@@ -112,4 +120,23 @@ abstract public class ReplayableActivity extends Activity
public void onPostExecuteCommand(Long refreshKey) public void onPostExecuteCommand(Long refreshKey)
{ {
} }
@Override
public void uncaughtException(Thread thread, Throwable ex)
{
try
{
ex.printStackTrace();
HabitsApplication.generateLogFile();
}
catch(Exception e)
{
// ignored
}
if(androidExceptionHandler != null)
androidExceptionHandler.uncaughtException(thread, ex);
else
System.exit(1);
}
} }

View File

@@ -0,0 +1,257 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
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.content.LocalBroadcastManager;
import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.BaseTask;
import java.util.Date;
public class HabitBroadcastReceiver extends BroadcastReceiver
{
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override
public void onReceive(final Context context, Intent intent)
{
switch (intent.getAction())
{
case ACTION_SHOW_REMINDER:
createNotification(context, intent);
createReminderAlarms(context);
break;
case ACTION_DISMISS:
dismissAllHabits();
break;
case ACTION_CHECK:
checkHabit(context, intent);
break;
case ACTION_SNOOZE:
snoozeHabit(context, intent);
break;
}
}
private void createReminderAlarms(final Context context)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
ReminderHelper.createReminderAlarms(context);
}
}, 5000);
}
private void snoozeHabit(Context context, Intent intent)
{
Uri data = intent.getData();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
long habitId = ContentUris.parseId(data);
Habit habit = Habit.get(habitId);
if(habit != null)
ReminderHelper.createReminderAlarm(context, habit,
new Date().getTime() + delayMinutes * 60 * 1000);
dismissNotification(context, habitId);
}
private void checkHabit(Context context, Intent intent)
{
Uri data = intent.getData();
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
long habitId = ContentUris.parseId(data);
Habit habit = Habit.get(habitId);
if(habit != null)
habit.repetitions.toggle(timestamp);
dismissNotification(context, habitId);
sendRefreshBroadcast(context);
}
public static void sendRefreshBroadcast(Context context)
{
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH);
manager.sendBroadcast(refreshIntent);
MainActivity.updateWidgets(context);
}
private void dismissAllHabits()
{
}
private void dismissNotification(Context context, Long habitId)
{
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habitId % Integer.MAX_VALUE);
notificationManager.cancel(notificationId);
}
private void createNotification(final Context context, final Intent intent)
{
final Uri data = intent.getData();
final Habit habit = Habit.get(ContentUris.parseId(data));
final Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
final Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
if (habit == null) return;
new BaseTask()
{
int todayValue;
@Override
protected void doInBackground()
{
todayValue = habit.checkmarks.getTodayValue();
}
@Override
protected void onPostExecute(Void aVoid)
{
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;
Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data);
PendingIntent contentPendingIntent =
PendingIntent.getActivity(context, 0, contentIntent, 0);
PendingIntent dismissPendingIntent = buildDismissIntent(context);
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
Notification notification =
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name)
.setContentText(habit.description)
.setContentIntent(contentPendingIntent)
.setDeleteIntent(dismissPendingIntent)
.addAction(R.drawable.ic_action_check,
context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze), snoozeIntentPending)
.setSound(soundUri)
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
notificationManager.notify(notificationId, notification);
super.onPostExecute(aVoid);
}
}.execute();
}
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
{
Uri data = habit.getUri();
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ACTION_SNOOZE);
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
}
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK);
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
}
public static PendingIntent buildDismissIntent(Context context)
{
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
deleteIntent.setAction(ACTION_DISMISS);
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
}
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);
}
private boolean checkWeekday(Intent intent, Habit habit)
{
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
boolean reminderDays[] = DateHelper.unpackWeekdayList(habit.reminderDays);
int weekday = DateHelper.getWeekday(timestamp);
return reminderDays[weekday];
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.app.Application;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.WindowManager;
import com.activeandroid.ActiveAndroid;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
public class HabitsApplication extends Application
{
@Nullable
private static Context context;
public static boolean isTestMode()
{
try
{
if(context != null)
context.getClassLoader().loadClass("org.isoron.uhabits.unit.models.HabitTest");
return true;
}
catch (final Exception e)
{
return false;
}
}
@Nullable
public static Context getContext()
{
return context;
}
@Override
public void onCreate()
{
super.onCreate();
HabitsApplication.context = this;
if (isTestMode())
{
File db = DatabaseHelper.getDatabaseFile();
if(db.exists()) db.delete();
}
DatabaseHelper.initializeActiveAndroid();
}
@Override
public void onTerminate()
{
HabitsApplication.context = null;
ActiveAndroid.dispose();
super.onTerminate();
}
public static String getLogcat() throws IOException
{
StringBuilder builder = new StringBuilder();
String[] command = new String[] { "logcat", "-d"};
Process process = Runtime.getRuntime().exec(command);
InputStreamReader in = new InputStreamReader(process.getInputStream());
BufferedReader bufferedReader = new BufferedReader(in);
String line;
while ((line = bufferedReader.readLine()) != null)
{
builder.append(line);
builder.append('\n');
}
return builder.toString();
}
public static String getDeviceInfo()
{
if(context == null) return "";
StringBuilder b = new StringBuilder();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME));
b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE));
b.append(String.format("OS Version: %s (%s)\n", System.getProperty("os.version"),
android.os.Build.VERSION.INCREMENTAL));
b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK));
b.append(String.format("Device: %s\n", android.os.Build.DEVICE));
b.append(String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL,
android.os.Build.PRODUCT));
b.append(String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER));
b.append(String.format("Other tags: %s\n", android.os.Build.TAGS));
b.append(String.format("Screen Width: %s\n", wm.getDefaultDisplay().getWidth()));
b.append(String.format("Screen Height: %s\n", wm.getDefaultDisplay().getHeight()));
b.append(String.format("SD Card state: %s\n\n", Environment.getExternalStorageState()));
return b.toString();
}
@NonNull
public static File generateLogFile() 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");
File dir = DatabaseHelper.getFilesDir("Logs");
if (dir == null) throw new IOException("log dir should not be null");
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.close();
return logFile;
}
}

View File

@@ -1,17 +1,20 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.uhabits;

View File

@@ -1,17 +1,20 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.uhabits;

View File

@@ -1,41 +1,70 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.uhabits;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import org.isoron.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.helpers.DialogHelper; import org.isoron.uhabits.helpers.UIHelper;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.fragments.ListHabitsFragment; import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.BaseTask;
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
import org.isoron.uhabits.widgets.FrequencyWidgetProvider;
import org.isoron.uhabits.widgets.HistoryWidgetProvider;
import org.isoron.uhabits.widgets.ScoreWidgetProvider;
import org.isoron.uhabits.widgets.StreakWidgetProvider;
public class MainActivity extends ReplayableActivity import java.io.File;
import java.io.IOException;
public class MainActivity extends BaseActivity
implements ListHabitsFragment.OnHabitClickListener implements ListHabitsFragment.OnHabitClickListener
{ {
private ListHabitsFragment listHabitsFragment; private ListHabitsFragment listHabitsFragment;
SharedPreferences prefs; private SharedPreferences prefs;
private BroadcastReceiver receiver;
private LocalBroadcastManager localBroadcastManager;
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
public static final int RESULT_IMPORT_DATA = 1;
public static final int RESULT_EXPORT_CSV = 2;
public static final int RESULT_EXPORT_DB = 3;
public static final int RESULT_BUG_REPORT = 4;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
@@ -47,15 +76,30 @@ public class MainActivity extends ReplayableActivity
listHabitsFragment = listHabitsFragment =
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1); (ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
receiver = new Receiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
onStartup(); onStartup();
} }
private void onStartup() private void onStartup()
{ {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false); PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
ReminderHelper.createReminderAlarms(MainActivity.this); UIHelper.incrementLaunchCount(this);
DialogHelper.incrementLaunchCount(this); UIHelper.updateLastAppVersion(this);
showTutorial(); showTutorial();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params)
{
ReminderHelper.createReminderAlarms(MainActivity.this);
updateWidgets(MainActivity.this);
return null;
}
}.execute();
} }
private void showTutorial() private void showTutorial()
@@ -87,15 +131,75 @@ public class MainActivity extends ReplayableActivity
switch (item.getItemId()) switch (item.getItemId())
{ {
case R.id.action_settings: case R.id.action_settings:
{
Intent intent = new Intent(this, SettingsActivity.class); Intent intent = new Intent(this, SettingsActivity.class);
startActivityForResult(intent, 0);
return true;
}
case R.id.action_about:
{
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
}
case R.id.action_faq:
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(getString(R.string.helpURL)));
startActivity(intent);
return true;
}
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (resultCode)
{
case RESULT_IMPORT_DATA:
listHabitsFragment.showImportDialog();
break;
case RESULT_EXPORT_CSV:
listHabitsFragment.exportAllHabits();
break;
case RESULT_EXPORT_DB:
listHabitsFragment.exportDB();
break;
case RESULT_BUG_REPORT:
generateBugReport();
break;
}
}
private void generateBugReport()
{
try
{
File logFile = HabitsApplication.generateLogFile();
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));
startActivity(intent);
}
catch (IOException e)
{
e.printStackTrace();
showToast(R.string.bug_report_failed);
}
}
@Override @Override
public void onHabitClicked(Habit habit) public void onHabitClicked(Habit habit)
{ {
@@ -108,5 +212,59 @@ public class MainActivity extends ReplayableActivity
public void onPostExecuteCommand(Long refreshKey) public void onPostExecuteCommand(Long refreshKey)
{ {
listHabitsFragment.onPostExecuteCommand(refreshKey); listHabitsFragment.onPostExecuteCommand(refreshKey);
new BaseTask()
{
@Override
protected void doInBackground()
{
updateWidgets(MainActivity.this);
}
}.execute();
}
public static void updateWidgets(Context context)
{
updateWidgets(context, CheckmarkWidgetProvider.class);
updateWidgets(context, HistoryWidgetProvider.class);
updateWidgets(context, ScoreWidgetProvider.class);
updateWidgets(context, StreakWidgetProvider.class);
updateWidgets(context, FrequencyWidgetProvider.class);
}
private static void updateWidgets(Context context, Class providerClass)
{
ComponentName provider = new ComponentName(context, providerClass);
Intent intent = new Intent(context, providerClass);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
context.sendBroadcast(intent);
}
@Override
protected void onDestroy()
{
localBroadcastManager.unregisterReceiver(receiver);
super.onDestroy();
}
class Receiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
listHabitsFragment.onPostExecuteCommand(null);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults)
{
if (grantResults.length <= 0) return;
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return;
listHabitsFragment.showImportDialog();
} }
} }

View File

@@ -1,207 +0,0 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
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 org.isoron.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
public class ReminderAlarmReceiver extends BroadcastReceiver
{
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
public static final String ACTION_REMIND = "org.isoron.uhabits.ACTION_REMIND";
public static final String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER";
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override
public void onReceive(final Context context, Intent intent)
{
switch (intent.getAction())
{
case ACTION_REMIND:
createNotification(context, intent);
createReminderAlarms(context);
break;
case ACTION_DISMISS:
dismissAllHabits();
break;
case ACTION_CHECK:
checkHabit(context, intent);
break;
case ACTION_SNOOZE:
snoozeHabit(context, intent);
break;
}
}
private void createReminderAlarms(final Context context)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
ReminderHelper.createReminderAlarms(context);
}
}, 5000);
}
private void snoozeHabit(Context context, Intent intent)
{
Uri data = intent.getData();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
Habit habit = Habit.get(ContentUris.parseId(data));
ReminderHelper.createReminderAlarm(context, habit,
new Date().getTime() + delayMinutes * 60 * 1000);
dismissNotification(context, habit);
}
private void checkHabit(Context context, Intent intent)
{
Uri data = intent.getData();
Long timestamp = DateHelper.getStartOfToday();
String paramTimestamp = data.getQueryParameter("timestamp");
if(paramTimestamp != null) timestamp = Long.parseLong(paramTimestamp);
Habit habit = Habit.get(ContentUris.parseId(data));
habit.toggleRepetition(timestamp);
habit.save();
dismissNotification(context, habit);
}
private void dismissAllHabits()
{
for (Habit h : Habit.getHighlightedHabits())
{
h.highlight = 0;
h.save();
}
}
private 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);
}
private void createNotification(Context context, Intent intent)
{
Uri data = intent.getData();
Habit habit = Habit.get(ContentUris.parseId(data));
if (habit.hasImplicitRepToday()) return;
habit.highlight = 1;
habit.save();
if (!checkWeekday(intent, habit)) return;
// Check if reminder has been turned off after alarm was scheduled
if (habit.reminderHour == null) return;
Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data);
PendingIntent contentPendingIntent =
PendingIntent.getActivity(context, 0, contentIntent, 0);
Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class);
deleteIntent.setAction(ACTION_DISMISS);
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
Intent checkIntent = new Intent(context, ReminderAlarmReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK);
PendingIntent checkIntentPending = PendingIntent.getBroadcast(context, 0, checkIntent, 0);
Intent snoozeIntent = new Intent(context, ReminderAlarmReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ACTION_SNOOZE);
PendingIntent snoozeIntentPending = PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
Notification notification =
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
.setContentTitle(habit.name)
.setContentText(habit.description)
.setContentIntent(contentPendingIntent)
.setDeleteIntent(deletePendingIntent)
.addAction(R.drawable.ic_action_check,
context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze), snoozeIntentPending)
.setSound(soundUri)
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
notificationManager.notify(notificationId, notification);
}
private boolean checkWeekday(Intent intent, Habit habit)
{
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
boolean reminderDays[] = DateHelper.unpackWeekdayList(habit.reminderDays);
int weekday = DateHelper.getWeekday(timestamp);
return reminderDays[weekday];
}
}

View File

@@ -1,17 +1,20 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.uhabits;

View File

@@ -1,36 +1,35 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits; package org.isoron.uhabits;
import android.app.ActionBar;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
public class ShowHabitActivity extends ReplayableActivity public class ShowHabitActivity extends BaseActivity
{ {
private Habit habit;
public Habit habit;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
@@ -39,34 +38,20 @@ public class ShowHabitActivity extends ReplayableActivity
Uri data = getIntent().getData(); Uri data = getIntent().getData();
habit = Habit.get(ContentUris.parseId(data)); habit = Habit.get(ContentUris.parseId(data));
getActionBar().setTitle(habit.name); ActionBar actionBar = getActionBar();
if (android.os.Build.VERSION.SDK_INT >= 21) if(actionBar != null && getHabit() != null)
{ {
getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color)); actionBar.setTitle(getHabit().name);
if (android.os.Build.VERSION.SDK_INT >= 21)
actionBar.setBackgroundDrawable(new ColorDrawable(getHabit().color));
} }
setContentView(R.layout.show_habit_activity); setContentView(R.layout.show_habit_activity);
} }
@Override public Habit getHabit()
public boolean onCreateOptionsMenu(Menu menu)
{ {
getMenuInflater().inflate(R.menu.show_habit_activity_menu, menu); return habit;
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.action_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
} }
} }

View File

@@ -1,26 +1,27 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.commands; package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.util.LinkedList;
import java.util.List; import java.util.List;
public class ArchiveHabitsCommand extends Command public class ArchiveHabitsCommand extends Command
@@ -28,12 +29,6 @@ public class ArchiveHabitsCommand extends Command
private List<Habit> habits; private List<Habit> habits;
public ArchiveHabitsCommand(Habit habit)
{
habits = new LinkedList<>();
habits.add(habit);
}
public ArchiveHabitsCommand(List<Habit> habits) public ArchiveHabitsCommand(List<Habit> habits)
{ {
this.habits = habits; this.habits = habits;
@@ -42,15 +37,13 @@ public class ArchiveHabitsCommand extends Command
@Override @Override
public void execute() public void execute()
{ {
for(Habit h : habits) Habit.archive(habits);
h.archive();
} }
@Override @Override
public void undo() public void undo()
{ {
for(Habit h : habits) Habit.unarchive(habits);
h.unarchive();
} }
public Integer getExecuteStringId() public Integer getExecuteStringId()

View File

@@ -1,29 +1,31 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.commands; package org.isoron.uhabits.commands;
import com.activeandroid.ActiveAndroid; import com.activeandroid.ActiveAndroid;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
public class ChangeHabitColorCommand extends Command public class ChangeHabitColorCommand extends Command
@@ -45,44 +47,25 @@ public class ChangeHabitColorCommand extends Command
@Override @Override
public void execute() public void execute()
{ {
ActiveAndroid.beginTransaction(); Habit.setColor(habits, newColor);
try
{
for(Habit h : habits)
{
h.color = newColor;
h.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
} }
@Override @Override
public void undo() public void undo()
{ {
ActiveAndroid.beginTransaction(); DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
try
{ {
int k = 0; @Override
for(Habit h : habits) public void execute()
{ {
h.color = originalColors.get(k++); int k = 0;
h.save(); for(Habit h : habits)
{
h.color = originalColors.get(k++);
h.save();
}
} }
});
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
} }
public Integer getExecuteStringId() public Integer getExecuteStringId()

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
public abstract class Command
{
public abstract void execute();
public abstract void undo();
public Integer getExecuteStringId()
{
return null;
}
public Integer getUndoStringId()
{
return null;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
public class CreateHabitCommand extends Command
{
private Habit model;
private Long savedId;
public CreateHabitCommand(Habit model)
{
this.model = model;
}
@Override
public void execute()
{
Habit savedHabit = new Habit(model);
if (savedId == null)
{
savedHabit.save();
savedId = savedHabit.getId();
}
else
{
savedHabit.save(savedId);
}
}
@Override
public void undo()
{
Habit habit = Habit.get(savedId);
if(habit == null) throw new RuntimeException("Habit not found");
habit.cascadeDelete();
}
@Override
public Integer getExecuteStringId()
{
return R.string.toast_habit_created;
}
@Override
public Integer getUndoStringId()
{
return R.string.toast_habit_deleted;
}
}

View File

@@ -1,22 +1,24 @@
/* Copyright (C) 2016 Alinson Santos Xavier /*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify it * This file is part of Loop Habit Tracker.
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT * Loop Habit Tracker is free software: you can redistribute it and/or modify
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * it under the terms of the GNU General Public License as published by the
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 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. * more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License along
* along with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.commands; package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
@@ -36,12 +38,14 @@ public class DeleteHabitsCommand extends Command
{ {
for(Habit h : habits) for(Habit h : habits)
h.cascadeDelete(); h.cascadeDelete();
Habit.rebuildOrder();
} }
@Override @Override
public void undo() public void undo()
{ {
throw new UnsupportedOperationException();
} }
public Integer getExecuteStringId() public Integer getExecuteStringId()

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
public class EditHabitCommand extends Command
{
private Habit original;
private Habit modified;
private long savedId;
private boolean hasIntervalChanged;
public EditHabitCommand(Habit original, Habit modified)
{
this.savedId = original.getId();
this.modified = new Habit(modified);
this.original = new Habit(original);
hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) ||
!this.original.freqNum.equals(this.modified.freqNum));
}
@Override
public void execute()
{
copyAttributes(this.modified);
}
@Override
public void undo()
{
copyAttributes(this.original);
}
private void copyAttributes(Habit model)
{
Habit habit = Habit.get(savedId);
if(habit == null) throw new RuntimeException("Habit not found");
habit.copyAttributes(model);
habit.save();
invalidateIfNeeded(habit);
}
private void invalidateIfNeeded(Habit habit)
{
if (hasIntervalChanged)
{
habit.checkmarks.deleteNewerThan(0);
habit.streaks.deleteNewerThan(0);
habit.scores.invalidateNewerThan(0);
}
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed_back;
}
}

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