Compare commits

..

709 Commits

Author SHA1 Message Date
16b0682229 Ignore all performance tests 2020-11-29 15:05:28 -06:00
a77798f293 PerformanceTest: Increase time limit 2020-11-29 13:38:09 -06:00
08050ff616 WidgetUpdater: Remove rate limit 2020-11-29 13:07:17 -06:00
12b080152b Speed up CreateHabitCommand and CreateRepetitionCommand 2020-11-29 12:46:01 -06:00
df3d660e83 SyncActivity: Remove password field 2020-11-29 07:19:40 -06:00
4908709296 Merge branch 'feature/sync' into dev 2020-11-29 07:12:12 -06:00
5717ae1bf1 RemoteSyncServer: Fix test 2020-11-29 07:11:38 -06:00
872c8d9d81 SyncManager: Small fix in logging 2020-11-29 06:47:18 -06:00
0b6110f0f9 Add alpha to version name 2020-11-28 22:29:24 -06:00
6df5e9ebe9 Monitor network availability; other minor fixes 2020-11-28 22:28:54 -06:00
2b9fd74a1d Close database 2020-11-28 22:28:46 -06:00
4a4b3c6aeb Server: change dir structure 2020-11-28 22:27:55 -06:00
7979f74bea Server: data persistence 2020-11-28 16:23:21 -06:00
b0336fb495 SyncManager: Log sync key 2020-11-28 16:22:33 -06:00
328fcd23f4 SyncManager: Run tasks in the same thread 2020-11-28 11:37:35 -06:00
9c0951ae58 Minor fixes to sync protocol 2020-11-28 11:32:28 -06:00
3f51561271 GitHub Actions: Upload only build outputs 2020-11-28 10:10:01 -06:00
1787c0e74e Upgrade to Android Gradle plugin 4.1.0 2020-11-28 10:06:53 -06:00
49faacda1c Wrap base64 functions; close gzip stream before reading 2020-11-28 09:44:45 -06:00
339eeff1ff Merge branch 'dev' into feature/sync 2020-11-28 08:55:12 -06:00
849212fd2f Run medium tests three times 2020-11-28 08:48:55 -06:00
356b2b06e4 Update README.md 2020-11-27 18:58:02 -06:00
b6eefbdb36 setSystemTime: Try two methods 2020-11-27 18:35:52 -06:00
2a72601153 Increase minSdkVersion to 23 2020-11-27 18:22:44 -06:00
2bf7358207 Fix medium tests for Android APIs 23-25 2020-11-27 18:00:56 -06:00
8c6e2ef461 GitHub Actions: Test multiple Android versions 2020-11-27 17:32:01 -06:00
ce0cbb6ee2 Sync: Improve encryption and preferences API 2020-11-27 10:55:55 -06:00
67ef3bb90c SyncManager: Switch to coroutines 2020-11-27 09:13:17 -06:00
b82af419f8 Merge branch 'dev' into feature/sync 2020-11-26 23:56:53 -06:00
49ff9a7edf Fix failing tests 2020-11-26 23:47:40 -06:00
53ebdf4f14 Skip upload if database has not changed 2020-11-26 23:43:47 -06:00
4762b54701 Merge branch 'dev' into feature/sync 2020-11-26 15:39:44 -06:00
aa288ac406 Make question marks optional
Fixes #96
2020-11-26 15:36:44 -06:00
2228dbf0f4 Create new UNKNOWN checkmark state 2020-11-26 15:08:49 -06:00
9ca1c8e459 Rewrite WidgetBehavior and associated tests 2020-11-26 14:19:15 -06:00
61414d62f4 Remove calls to Repetition.add and Repetition.remove 2020-11-26 14:19:02 -06:00
f97fed3b9b Repetition: Replace toggle by setValue 2020-11-26 13:29:12 -06:00
d45281d137 Merge branch 'master' into dev 2020-11-26 08:59:52 -06:00
1bb6ad41b2 Merge branch 'hotfix/1.8.10' 2020-11-26 08:22:50 -06:00
56c180183e Update release notes 2020-11-26 08:22:37 -06:00
af8d983cca Add dummy settings.gradle file to help F-Droid locate app metadata 2020-11-26 08:20:10 -06:00
0a49232ebd Add dummy settings.gradle file to help F-Droid locate app metadata 2020-11-26 08:11:40 -06:00
cff8e26428 Update translations 2020-11-25 22:33:41 -06:00
e892bccb32 Bump version to 1.8.10 2020-11-25 18:23:27 -06:00
68ccf37fd5 Add UUID to habits 2020-11-24 08:28:16 -06:00
659c528744 SyncManager: First version 2020-11-24 06:55:37 -06:00
b1560dd694 LoopDBImporter: Use commands instead of directly modifying DB 2020-11-24 06:54:28 -06:00
0de86ac66c Update widgets at most once per minute 2020-11-24 06:53:20 -06:00
06e5d517cc Downgrade Ktor 2020-11-24 06:51:34 -06:00
35ca041bc2 EncExt: Trim keys 2020-11-24 06:51:08 -06:00
576a334dc9 Update sync intent-filter 2020-11-24 06:32:21 -06:00
294aee5d12 Implement cryptography extensions 2020-11-22 22:39:22 -06:00
0859cec853 Implement intent filter; hide password for now 2020-11-22 17:00:37 -06:00
23f2978a64 Add sync preferences to settings screen 2020-11-22 15:39:08 -06:00
a2400172e2 Make registration functional 2020-11-22 13:00:59 -06:00
5376f4bff8 Implement SyncActivity (with static data) 2020-11-22 10:07:34 -06:00
0497890cb0 Update docker registry URL 2020-11-22 10:02:53 -06:00
2848c4e77b Server: Implement get version 2020-11-22 09:49:35 -06:00
8fa3ba1b18 Add docker tasks to gradle 2020-11-21 20:49:48 -06:00
b4f36dd258 Initial version of Ktor sync server 2020-11-21 20:12:14 -06:00
008902d3b7 AutoBackup: Use getLocalTime instead of getStartOfToday; improve logging 2020-11-21 10:12:52 -06:00
4764c07f3b Bump version to 2.0.0 2020-11-19 19:29:26 -06:00
8f0cfa8614 Update CHANGELOG.md 2020-11-19 19:27:27 -06:00
865e1969e6 Update CHANGELOG 2020-11-19 19:22:10 -06:00
Quentin Hibon
bfddc42f5e Allow user to sort by status (#660) 2020-11-19 19:05:21 -06:00
dc0b8deccf Export backups daily
Fixes #178
2020-11-19 18:31:28 -06:00
b674d14b49 Opt-in skips: Update tests 2020-11-18 22:26:45 -06:00
d594d3b085 Make skip days an opt-in feature 2020-11-18 22:13:03 -06:00
bef85bf93a CHANGELOG: Minor fixes 2020-11-18 07:16:17 -06:00
76eaefc95b Merge branch 'master' into dev 2020-11-18 07:11:45 -06:00
83c1ab35d5 GH Actions: Update publish.yml 2020-11-18 06:57:22 -06:00
7a6563736a Merge branch 'hotfix/1.8.9' 2020-11-18 06:47:44 -06:00
55c50c1119 Update CHANGELOG 2020-11-18 06:47:29 -06:00
ba08968600 Fix tests 2020-11-18 06:45:47 -06:00
Sunxy88
2d488a67f2 Use dark theme in settings window. (#655) 2020-11-14 07:57:07 -06:00
Kristian Tashkov
d997b1378d Setting custom start of the day (#621) 2020-09-19 19:23:00 -05:00
720f98f9bd Write tests for IntentScheduler 2020-09-19 19:11:57 -05:00
ddea9e78a9 ScoreList: Fix interaction between SKIP and rolling sum 2020-09-16 07:38:59 -05:00
c429cb41c0 Fix method rename 2020-09-15 22:32:46 -05:00
ae286cec14 Take frequency into account when computing score for numerical habits 2020-09-15 22:30:44 -05:00
31d631b155 Update test screenshots 2020-09-15 22:30:44 -05:00
20142d5f94 Reduce time required to form non-daily habits; smooth out irregular schedules 2020-09-15 22:30:44 -05:00
ef186d55c6 Update test screenshots 2020-09-15 22:30:44 -05:00
8b847ae9fa ScoreList: Use rolling sum method also for boolean habits
See #641
2020-09-15 22:30:44 -05:00
Kristian Tashkov
a4ef657897 Make skips freeze score (#630) 2020-09-13 17:01:15 -05:00
f44556e281 ScoreList: Use rolling sum for non-daily numerical habits 2020-09-12 21:40:59 -05:00
8a895b2d20 Fix colors in BarChart and HistoryChard 2020-09-12 16:09:12 -05:00
61f32449dd Numerical habits: allow Tasker to increment/decrement value 2020-09-12 15:10:46 -05:00
Kristian Tashkov
07f8583c3d Don't show reminders from archived habits (#639) 2020-09-12 13:15:23 -05:00
Kristian Tashkov
69f11c9d4e Fix clearing of reminders (#638) 2020-09-12 12:47:03 -05:00
Nguyen Ly Nam
1ffc079042 Numerical habits: update notifications and detail screen (#627) 2020-09-11 17:32:20 -05:00
5fa3f412c0 Show YES_AUTO as grey checkmark
This reverts a change introduced recently where YES_AUTO (previously CHECKED_IMPLICITLY)
was shown as a grey dash.
2020-09-05 18:46:48 -05:00
b72cad5316 Rename checkmark values to NO, YES_AUTO, YES_MANUAL and SKIP
This makes the source code consistent with the user interface.
2020-09-05 18:04:04 -05:00
Kristian Tashkov
d59ab89426 Update widgets at midnight (#634) 2020-09-05 17:25:46 -05:00
ea019321e6 Revert "NumberPickerFactory: Show and hide keyboard using InputMethodManager"
This reverts commit 6967def950. InputMethodManager method
does not work reliably on widgets. It also cannot reliably hide the keyboard.
2020-09-03 22:22:07 -05:00
6967def950 NumberPickerFactory: Show and hide keyboard using InputMethodManager
In about 1 every 10 attempts, the previous solution randomly failed to show
the keyboard, although the text field was focused. This solution seems more reliable.
2020-09-03 21:55:03 -05:00
6d4cac427f NumberPickerFactory: Automatically show keyboard 2020-09-03 21:06:46 -05:00
152b2d5427 Merge pull request #631 from KristianTashkov/habit_selection_dark_theme
Fix habit selection menu item background color
2020-09-02 08:09:26 -05:00
9d28fbe7b5 ScoreList: Revert recent changes to computation of scores 2020-09-02 07:01:54 -05:00
c846dfc75a Make skips equivalent to implicit checks; make their visual representation consistent 2020-09-01 22:23:42 -05:00
ee7eb4ef51 build.sh: Always use GRADLE variable 2020-09-01 22:22:38 -05:00
KristianTashkov
57bfe3d801 Fix habit selection menu item background color 2020-09-01 15:53:30 +03:00
a5ee96f988 HistoryChart: Make skipped days a bit more clear 2020-08-23 15:09:13 -05:00
7b0eddeac5 Set minimum widget size 2020-08-23 14:48:58 -05:00
e8e52db9b1 HabitPickerDialog: Show "no habits found" message 2020-08-23 14:39:11 -05:00
cddbf558e6 Reactivate widget view tests; update widget previews 2020-08-23 14:09:50 -05:00
79459c373e Manage exceptions for when activities don't exist to handle intents
Fixes #181
2020-08-23 08:57:00 -05:00
84523869e8 Manage exceptions for when activities don't exist to handle intents
Fixes #181
2020-08-23 08:43:22 -05:00
590298bf5b MemoryHabitList: Inherit parent's order
Fixes #598
2020-08-23 07:43:20 -05:00
8067fd5313 MemoryHabitList: Inherit parent's order
Fixes #598
2020-08-23 07:41:42 -05:00
Christoph Hennemann
d2dc756a34 Improve readability of history and streak charts
Fixes #432

Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-22 23:37:43 -05:00
2af1dbf3a6 Reactivate HistoryChart tests; fix IndexOutOfBoundsException 2020-08-22 19:42:46 -05:00
ebab6f08ee Remove obsolete regression test 2020-08-22 19:04:29 -05:00
4a2b21855a Update ListHabitsRegressionTest 2020-08-22 19:04:11 -05:00
42d5edec26 WidgetTest: Tap twice to remove checkmark 2020-08-22 18:50:39 -05:00
f368e43158 CreateRepetitionCommand: Run update() after executing 2020-08-22 18:27:12 -05:00
09eb8c9f4d Fix widget tests on API 29+ 2020-08-22 17:52:53 -05:00
209e709163 Make widgets fully opaque by default 2020-08-22 16:26:59 -05:00
d20a2be7e6 Remove obsolete test screenshots 2020-08-22 15:58:26 -05:00
bd68f8fc5a Revert changes to HistoryCard view screenshots 2020-08-22 15:56:24 -05:00
KristianTashkov
1a05f7d85d Allow user to skip days without breaking streak
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-22 15:35:35 -05:00
TacoTheDank
d9ff429c28 Deprecation fixes
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-22 12:14:35 -05:00
3554895a5d Widgets: Show shadow if widget is completely opaque 2020-08-18 08:09:12 -05:00
16491c142a Widgets: Fix title size 2020-08-18 08:00:38 -05:00
859fea5ff5 Hide streaks for numerical habits 2020-08-18 07:57:57 -05:00
34c73e89db Merge branch 'hotfix/1.8.9' into dev 2020-08-15 15:18:21 -05:00
09bf49a9ce Remove notification groups; revert to default system behavior 2020-08-15 14:45:40 -05:00
48e43869c7 Merge branch 'hotfix/1.8.9' into dev 2020-08-15 14:18:50 -05:00
963fb58309 Revert changes to android-pickers 2020-08-15 13:58:03 -05:00
38fb37cde2 Remove SyncManager and Internet permission 2020-08-15 13:54:29 -05:00
d0b4e3e163 Bump version to 1.8.9 2020-08-15 13:35:48 -05:00
3ef3be4d16 Tidy up strings.xml 2020-08-15 12:47:32 -05:00
bae0e3bcc1 Remove unused resources 2020-08-15 12:45:34 -05:00
3e99d821a5 Allow user to sort habits in reverse order
Closes #556, closes #497
2020-08-15 12:29:49 -05:00
olegivo
acb5051eec Konvert BaseActivity
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-15 11:46:13 -05:00
olegivo
b76882dd1d Konvert BaseMenu
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-15 11:41:19 -05:00
olegivo
978946baab Konvert BaseSelectionMenu
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-15 11:38:41 -05:00
olegivo
d202f14c14 Konvert BaseScreen
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-15 11:36:03 -05:00
olegivo
17a85e517a Konvert BaseRootView
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-13 07:53:37 -05:00
olegivo
c5bc5deff0 Konvert ActivityScope
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-13 07:44:07 -05:00
olegivo
b7f04957a5 Konvert ActivityContext
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-13 07:43:19 -05:00
olegivo
b0f5f96eee Konvert StyledResources
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-13 07:40:49 -05:00
olegivo
fd76a3c6fd Konvert InterfaceUtils
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-08-13 07:32:41 -05:00
b31482881b Merge pull request #608 from KristianTashkov/kris/fix_yes_no
Only save numerical habit features if the habit is numerical
2020-08-04 07:29:50 -05:00
KristianTashkov
87231d7fa4 fix saving of non-numerical habits 2020-07-20 13:12:58 +03:00
olegivo
4d18a1335c Convert FileUtils to Kotlin
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-07-12 10:22:18 -05:00
olegivo
424a417a13 Convert ColorUtils to Kotlin
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-07-12 10:00:23 -05:00
Thomas S
96d23bdf22 Update checkmark widget for numerical support
Co-authored-by: Alinson S. Xavier <git@axavier.org>
2020-07-12 09:25:49 -05:00
a8e77b8df8 Update test screenshots 2020-07-02 08:04:03 -05:00
5413569ce3 Update test screenshots 2020-07-02 07:31:02 -05:00
d80b85ac8c Make bars round 2020-06-25 07:16:47 -05:00
40bc35935f Create target widget 2020-06-25 07:04:43 -05:00
a6060f468d Create new TargetCard and TargetChart 2020-06-25 06:12:26 -05:00
6ec9d51a1e CheckmarkList: Implement getThisIntervalValue 2020-06-24 20:07:56 -05:00
de28a5e74e EditHabitActivity: Do not divide target by freq denominator 2020-06-24 20:07:21 -05:00
3ba503604b Show target in SubtitleCard; replace some bitmap icons by FontAwesome 2020-06-23 06:19:08 -05:00
6d48b53861 Merge branch 'master' into dev 2020-06-21 17:28:41 -05:00
0cce6b30b1 Merge branch 'hotfix/1.8.8' 2020-06-21 11:39:25 -05:00
bf650a7565 Fix crash when saving habit (some languages only) 2020-06-21 11:31:53 -05:00
b78cd1dd0d Update tests 2020-06-21 11:00:00 -05:00
6d9ad8c56c Tweak snapIntervalsTogether so that "1 time every x days" habits work better 2020-06-21 10:59:31 -05:00
e7a3f0cffa Fix build script; remove some obsolete tests 2020-06-21 10:45:46 -05:00
6aa72caf6c Bump version to 1.8.8 2020-06-21 10:39:27 -05:00
0b7697d172 Make save button functional for numerical habits 2020-06-20 08:39:55 -05:00
b9850fa085 Create form for numerical habits 2020-06-20 07:45:38 -05:00
ecb3978bdd Fix dialog animations 2020-06-19 20:57:35 -05:00
fc57a9db6c Merge branch 'feature/numerical' into dev 2020-06-19 07:50:50 -05:00
6c9c2a6c1a Update tests 2020-06-19 07:48:40 -05:00
3e0529d515 Remove old EditHabitDialog 2020-06-19 06:59:16 -05:00
7d8d89fbbd EditHabitActivity: Adjust inputType 2020-06-19 06:58:36 -05:00
c43f3c2fd7 EditHabitActivity: Dismiss all fragments on device rotation 2020-06-19 06:39:21 -05:00
403ed8b250 EditHabitActivity: Make save button functional 2020-06-19 06:24:51 -05:00
9ccb2b2737 Implement reminder day picker 2020-06-18 22:19:59 -05:00
424a282847 Update gitignore 2020-06-18 07:45:19 -05:00
309b6cbcaf Implement reminder time picker; customize picker color 2020-06-18 07:43:58 -05:00
72ad14119a Create HabitTypeDialog 2020-06-17 07:00:25 -05:00
Alex Johnson
652ed50d09 Reset habit strength graph scroll location when switching scale
- Create ScrollableChart.reset() to set x coordinate of scroller
- Call reset() from doInBackground() in ScoreCard to reset scroll location
2020-06-16 20:49:14 -07:00
6070a7af2e Merge branch 'dev' into edit-redesign 2020-06-16 07:19:57 -05:00
8fd8c2802b Remove AboutBehavior and AboutModule 2020-06-16 06:44:28 -05:00
923b923745 build.sh: Remove .gradle directory when cleaning project 2020-06-16 06:43:38 -05:00
59c8031372 Minor style changes 2020-06-15 08:27:48 -05:00
0058089e7d Remove redundant repositories section 2020-06-15 08:27:31 -05:00
d5a840388c Merge branch 'dev' into 2kotlin-androidbase 2020-06-15 08:00:34 -05:00
4b07d7d5b1 Fix build script; remove some obsolete tests 2020-06-15 07:59:35 -05:00
2fffc25128 Merge pull request #585 from olegivo/update-agp 2020-06-15 06:19:27 -05:00
4a4501276c Merge pull request #590 from Gelma/typos
Fix typos
2020-06-15 06:16:54 -05:00
Andrea Gelmini
c8e3735dd6 Fix typos 2020-06-13 18:25:05 +02:00
olegivo
61267e40e7 konvert SSLContextProvider 2020-06-04 11:56:03 +03:00
olegivo
c0b664e1e4 konvert BaseExceptionHandler 2020-06-04 11:56:03 +03:00
olegivo
e57c319658 konvert @AppContext 2020-06-04 11:56:03 +03:00
olegivo
e54ba826b3 konvert AndroidDirFinder
remove if-null condition cause ContextCompat.getExternalFilesDirs is @NonNull
2020-06-04 11:56:03 +03:00
olegivo
9b8784b4c4 AndroidBugReporter: more idiomatic kotlin 2020-06-04 11:56:03 +03:00
olegivo
51a7b7a7d4 konvert AndroidBugReporter 2020-06-04 11:56:03 +03:00
olegivo
d761b474cf add kotlin support for android-base project 2020-06-04 11:56:03 +03:00
olegivo
51be585b9d update AGP (4.0.0) 2020-06-04 11:14:52 +03:00
olegivo
76be5037fd update AGP (3.6.3) 2020-05-18 17:37:39 +03:00
aee0da2c64 Merge branch 'feature/kn-update' into dev 2020-05-17 14:54:36 -05:00
f1610e6603 core: Skip BaseViewTests on iOS 2020-05-17 14:54:02 -05:00
a7a1766809 core: update gradle, rename iOS framework 2020-05-17 14:44:00 -05:00
5f83314d56 Fix format method on JVM and JS 2020-05-17 13:33:22 -05:00
c784f40c55 Remove custom iosTest task; fix IosDatabase 2020-05-17 13:28:52 -05:00
6a172d135b Update to K/N 1.3.72; remove i18n classes; rewrite sprintf 2020-05-17 11:47:13 -05:00
13f4981066 Merge pull request #547 from recheej/rechee/add_notes
Add Notes to Habits.
2020-03-01 15:11:38 -05:00
Rechee Jozil
849b91dde2 delete bad unit test 2020-02-01 11:08:32 -08:00
Rechee Jozil
66b4c48d92 null check description 2020-02-01 11:08:06 -08:00
Rechee Jozil
2e64da4cac using wildcard imports 2020-02-01 11:03:14 -08:00
323ddcc11a Implement FrequencyPickerDialog 2020-01-20 06:08:29 -06:00
175000efd1 Follow current theme; implement color switching 2020-01-12 11:54:15 -06:00
6f94fc48c1 First version of EditHabitActivity 2020-01-12 10:20:36 -06:00
Rechee
18d1d0d9f7 fixed screenshot tests 2020-01-09 19:49:14 -08:00
Rechee
47edea47ae creating UI test for blank description 2020-01-08 20:35:45 -08:00
Rechee
1714cf8050 added create habit test for description 2020-01-08 20:16:43 -08:00
Rechee
7366e9a47f add blank habit test 2020-01-08 20:04:11 -08:00
Rechee
e58589cfbd adding description to test 2020-01-08 20:02:38 -08:00
Rechee
2999e0e5eb updating test 2020-01-08 19:50:17 -08:00
Rechee
fa7bc27124 add test for notes Card 2020-01-08 19:36:54 -08:00
Rechee
f5be9d3c67 introduced notes card to support refresh 2020-01-08 19:25:23 -08:00
Rechee
2c46e8909a now showing notes in show habits 2020-01-08 18:57:47 -08:00
Rechee
8b042f30dc displaying subtitle card UI in designer
squash! displaying subtitle card UI in designer
2020-01-08 18:39:30 -08:00
Rechee
46761926d2 now writing question and description to csv 2020-01-08 17:59:26 -08:00
Rechee
88d6a8e513 fix habit importers to use description instead 2020-01-08 17:52:52 -08:00
Rechee
557ae19297 setting description to be blank instead of null 2020-01-08 17:45:55 -08:00
Rechee
9c10a56dda setting question label in subtitle 2020-01-07 22:25:14 -08:00
Rechee
895b068321 fixing tests 2020-01-07 21:32:18 -08:00
Rechee
fb98c5fe9a replacing getDescription with getQuestion all over the code 2020-01-07 20:39:23 -08:00
Rechee
0ec604f21e fix formatting 2020-01-07 20:34:18 -08:00
Rechee
bcd9dd1bb5 now allowing blank for description
squash! now allowing blank for description
2020-01-07 20:33:39 -08:00
Rechee
61bcd253f8 fully implementing question & description in UI and code 2020-01-07 20:22:35 -08:00
Rechee
fb40dbdabc edit name description panel xml 2020-01-07 20:02:55 -08:00
Rechee
0990192cd6 making nullable 2020-01-06 17:46:48 -08:00
Rechee
1cf2d69534 creating migration tests 2020-01-06 17:40:11 -08:00
a3344358b9 Shorten release notes 2020-01-06 18:43:27 -06:00
22fcecb48c Merge branch 'dev' 2020-01-06 18:27:24 -06:00
58d8c799ce Bump version to 1.8.7 2020-01-06 18:25:37 -06:00
f9437d61b0 Fix issue causing multiple notifications to be dismissed at once (#540) 2020-01-06 18:24:53 -06:00
Rechee
0489dc39e0 updating db version and adding migration for question column 2020-01-06 12:46:05 -08:00
Rechee
88b9645be1 adding question to habit and habit record 2020-01-06 12:36:25 -08:00
997ebfc28a ListHabitsRegressionTest: update imports 2020-01-05 19:01:28 -06:00
6a91300d82 Migrate to AndroidX (#544) 2020-01-05 18:47:01 -06:00
889ce9faef Merge branch 'dev' 2020-01-05 08:30:21 -06:00
b3a40efe46 Bump version to 1.8.6 2020-01-05 08:29:56 -06:00
547c7ffdf7 Fix ID assignment on SQLiteHabitList 2020-01-05 08:29:49 -06:00
olegivo
82486c7514 migrate InstrumentationRegistry 2020-01-05 12:24:36 +03:00
olegivo
fe732ea385 manual migration 2020-01-05 12:24:36 +03:00
olegivo
85ec0faa99 update butterknife (10.2.1) for support jetifier 2020-01-05 12:24:27 +03:00
olegivo
9ac1ae9915 automatic migration 2020-01-05 11:22:58 +03:00
895d66b663 Merge branch 'dev' 2020-01-04 09:01:36 -06:00
0f2a93cd27 Bump version to 1.8.5 (48) 2020-01-04 08:58:09 -06:00
323c98edb8 Fix ReminderSchedulerTest 2020-01-04 08:44:42 -06:00
aadfac68cd Bring back snooze button 2020-01-03 19:56:19 -06:00
7076cffec5 Disable more flaky tests 2020-01-03 18:13:01 -06:00
0ef5a8dead Merge branch 'dev' 2020-01-03 17:55:47 -06:00
526c8fe750 Bump version to 1.8.4 (47) 2020-01-03 17:55:20 -06:00
464eaf613d Fix some empty CSV files 2020-01-03 17:46:46 -06:00
b21d77514a Increase logging for ReminderReceiver and WidgetReceiver 2020-01-03 17:46:46 -06:00
58ed759224 Disable notification bundling; add default question 2020-01-03 17:46:42 -06:00
7c742e1016 Make HabitCardListCache more thread-safe; add null checks 2020-01-03 17:09:39 -06:00
2e9330b5c5 Merge branch 'dev' 2020-01-03 13:27:02 -06:00
d1b83d069d Loop 1.8.3 2020-01-03 13:20:09 -06:00
7d35a85a37 Disable some flaky tests 2020-01-03 13:19:34 -06:00
616d9ab46c Put SavedState tests together 2020-01-03 12:59:59 -06:00
b9c9d6852a Merge branch 'edi_habit_crash' into dev 2020-01-03 09:00:35 -06:00
29e4a8d4ec Minor style changes 2020-01-03 09:00:17 -06:00
Rechee
ceed784acd remove unused tag 2020-01-03 08:56:50 -06:00
Rechee
897887f802 optimize imports 2020-01-03 08:56:50 -06:00
Rechee
dcd0c61b8d fixed issue where weekly picker dialog isn't being restored on rotation
squash! fixed issue where weekly picker dialog isn't being restored on rotation
2020-01-03 08:56:50 -06:00
1285b653ed Fix imports 2020-01-03 08:56:40 -06:00
dfd5c65595 Create failing regression test for issue #534 2020-01-03 08:15:52 -06:00
315bddea96 Automatically fix invalid timestamps, instead of crashing 2020-01-03 06:40:30 -06:00
938739d535 Update app description 2020-01-02 17:47:06 -06:00
27f625873d Reorganize .secret 2020-01-02 15:59:59 -06:00
f37d25e86b Loop 1.8.2 2020-01-02 12:58:26 -06:00
b2df70c059 Add Google Play Publisher plugin and Play Store assets 2020-01-02 12:55:44 -06:00
3afd46c59c Remove unused HabitLogger class 2020-01-02 05:41:03 -06:00
e36c649333 Fix unit tests 2020-01-02 05:31:51 -06:00
b2e7c9fe6e Remove HabitNotFound exception from WidgetPreferences 2020-01-02 05:27:17 -06:00
2171d582dc Fix some issues with reminders and print more logs 2020-01-02 05:12:44 -06:00
b7be459537 Create main GitHub Actions workflow 2020-01-01 20:54:51 -06:00
TacoTheDank
cbee09c38f Update gradle scripts and dependencies (#538)
* Update gradle wrapper
* Clean up some unnecessary/unused things in android gradles
* Update some dependencies
2020-01-01 18:03:53 -06:00
6f24e42d1f Disable write-ahead log on SQLite
Android 9 (Pie) silently introduced a new behavior where new SQLite
databases have Write-Ahead Logs (WALs) enabled by default. The data,
even after committed, is not written to the main database file until
later. This caused a silent bug leading to data loss, where exported
backups would either be empty or miss some recent data.
2020-01-01 09:40:35 -06:00
93bb105dac Remove gradle git plugin 2020-01-01 08:45:05 -06:00
f67b74d57d Update CHANGELOG 2020-01-01 08:31:16 -06:00
abbf1e4d66 Update translations 2020-01-01 08:29:44 -06:00
e3f6353062 Preserve widgets from Loop 1.7.11 and lower versions 2020-01-01 08:11:37 -06:00
38d5b2bf16 build.sh: Rename env variables; copy final APK to build/ 2020-01-01 07:46:29 -06:00
7801c933f0 Allow user to change first day of the week
Closes #421
2019-12-31 14:12:17 -06:00
5cdb9eb9d5 Settings: Remove extra margin 2019-12-31 11:23:02 -06:00
dee4670f65 Update CHANGELOG 2019-12-31 11:11:06 -06:00
50a6d7190b Revert change to launcher name 2019-12-30 17:29:24 -06:00
d3371badf8 Make the short name of the app untranslatable 2019-12-30 17:07:59 -06:00
ed1f3e5cdb Update translations 2019-12-30 17:05:46 -06:00
5947926cd5 Rename launcher icon to "Loop" 2019-12-30 16:39:47 -06:00
61cd77e5df Update screenshots 2019-12-30 16:17:17 -06:00
d6ec33d6d3 Fix failing unit test 2019-12-30 16:03:56 -06:00
3f002efb53 Allow user to change transparency of widgets
Closes #376
2019-12-30 16:03:56 -06:00
5191290188 Fix EditHabitDialog theme 2019-12-30 16:03:56 -06:00
cc05543692 Improve performance of detail page by cancelling useless async tasks 2019-12-30 16:03:56 -06:00
Brian Norman
e94e0c057e Capitalize sentences on dialogFormInputMultiline (#535) 2019-12-30 02:46:30 -06:00
350f002ed3 Update translations and credits 2019-12-29 16:15:37 -06:00
04cf4e7785 Update CHANGELOG 2019-12-29 14:31:54 -06:00
bf644f4e09 Rename night mode to dark theme; abide by system-wide settings (API 29)
Closes issue #513
2019-12-29 12:22:19 -06:00
3658aef2e2 Use dark navigation bar in dark theme 2019-12-29 11:42:10 -06:00
190a1171c4 Bump minSdkVersion to 21 (Lollipop) 2019-12-29 11:29:24 -06:00
2b20b6bb9d Add link to privacy policy 2019-12-29 11:12:29 -06:00
caed1aef79 Hide widget stacks feature for non-developers 2019-12-29 08:24:07 -06:00
8341956a90 Hide numerical habits feature for non-developers 2019-12-29 08:24:07 -06:00
de44b48dba Make all tests pass on Android 9.0 (Pie) 2019-12-29 08:24:07 -06:00
7eafd92b2d Make all tests pass on Android 7.1.1 (Nougat) 2019-12-29 08:24:07 -06:00
47dc26fea0 Fix typo in TEST.md 2019-11-17 19:32:04 -06:00
a6dd0939b2 Add TEST.md 2019-11-17 19:12:14 -06:00
898a33a754 Make all tests pass on Android 6.0 (Marshmallow) 2019-11-17 18:23:07 -06:00
1997d9491b Make all tests run successfully on Lollipop 2019-11-17 17:46:36 -06:00
6d57ea0368 Merge branch 'master' into dev 2019-11-16 23:00:26 -06:00
83c1197dc1 Increase target SDK to 29; update support library 2019-11-16 22:31:19 -06:00
48d145626f Update to Build Tools 3.5.2 2019-11-16 22:18:14 -06:00
a57f310c76 Remove DexCount plugin 2019-11-16 22:14:07 -06:00
1e6cb2e841 Remove PlayPublisher plugin 2019-11-16 22:06:19 -06:00
c676a02ca8 Remove Jacoco 2019-11-16 22:04:11 -06:00
ded57cd04a Merge branch 'dev' into agp-update 2019-11-16 22:00:13 -06:00
b8f9e2f309 Update to Dagger 2.25.2 2019-11-16 21:56:49 -06:00
olegivo
2fb9168686 Convert IntroActivity to Kotlin (#520) 2019-11-16 21:16:20 -06:00
9199a64d73 Pick specific Jacoco version (0.8.4) 2019-11-16 21:15:00 -06:00
Mick Dekkers
a69fb369df Preserve selections when reopening EditSettingActivity (#524) 2019-11-10 08:41:10 -06:00
0790961bb5 BUILD.md: add Android Studio instructions 2019-11-10 08:03:57 -06:00
c88fa4a003 First version of BUILD.md 2019-11-09 20:44:56 -06:00
f403dfd7d1 Disable failing tests 2019-11-09 18:37:21 -06:00
105baf629a Flip bar chart 2019-11-08 21:40:49 -06:00
oleg.industry
af37036ac5 jacoco concrete version fixed 2019-10-28 11:45:08 +03:00
oleg.industry
8717ad6ad0 kotlin update (1.3.50) 2019-10-28 11:45:08 +03:00
olegivo
95af0217ff google repository simplification 2019-10-08 14:25:18 +03:00
olegivo
fe091fa740 update AGP (3.5.1)
using default build tools version (from AGP)
2019-10-08 14:25:13 +03:00
olegivo
00abb4486d update libraries
auto-factory (1.0-beta6)
butterknife (9.0.0)
guava (24.1-android)
2019-10-08 14:24:21 +03:00
olegivo
dadfcb7c16 extracted AUTO_FACTORY_VERSION 2019-10-08 14:24:21 +03:00
Veyndan Stuart
77a9701805 Convert PaletteUtils to Kotlin (#510)
* Convert PaletteUtils to Kotlin
2019-10-05 07:31:32 -04:00
bfea4b024a core: Implement bar chart 2019-08-18 07:47:46 -05:00
Tthecreator
3e2cf48223 Changed habit type selection to dropdown instead of popup message (#498) 2019-08-12 12:39:44 -05:00
ec2fa16fab Web: update npm modules 2019-08-12 06:27:20 -05:00
8d97a8d140 Re-enable view tests 2019-08-12 06:10:56 -05:00
563aa8b7b4 Finish Kotlin implementation of IosCanvas 2019-08-12 06:01:32 -05:00
74475bd191 Partial implementation of IosCanvas in Kotlin 2019-08-11 17:11:29 -05:00
957a5b7c17 Fix CSV export in some locales; bump version to 1.7.11 (38) 2019-08-10 11:42:25 -05:00
64cc9f78a8 Increase targetSdk to 28 2019-06-15 09:46:06 -05:00
e50c411d1e Fix crash preventing some Xiaomi devices from showing notifications 2019-06-15 08:57:11 -05:00
046a7eab7f Move IosDatabase to Kotlin 2019-06-09 13:16:29 -05:00
d96732b588 Update 2019-06-07 07:11:47 -05:00
a3bfc05068 Move view tests to common 2019-04-14 10:33:36 -05:00
defa2f9431 Reorganize source tree 2019-04-13 22:54:26 -05:00
fe4139e268 Move tests to commonTest; convert some classes from Swift to Kotlin 2019-04-13 22:28:05 -05:00
d463bb55d7 Unify async tests 2019-04-13 19:27:51 -05:00
5ea19c9475 Restore Backend class; replace TaskRunner by Kotlin Coroutines 2019-04-13 09:20:37 -05:00
b0cedde0a9 Partial kotlin implementation of IosCanvas 2019-04-12 05:17:31 -05:00
e0894c9313 Move IosFiles implementation to Kotlin; setup tests for ios target 2019-04-12 05:16:31 -05:00
8378d88186 Update gradle 2019-04-11 08:13:19 -05:00
ddd363917c Implement JsFileStorage using IndexedDB 2019-04-10 20:07:15 -05:00
f310eaf7d9 Make I/O asynchronous with coroutines; make all JS tests pass 2019-04-10 08:20:13 -05:00
8972f2d03d Move most tests to commonTest 2019-04-10 05:35:56 -05:00
e88c58916a Implement JsFiles 2019-04-10 04:52:17 -05:00
7d169d8053 Update copyright date 2019-04-09 08:46:25 -05:00
c018d89ca6 Implement JsDatabase using sql.js 2019-04-09 08:43:23 -05:00
5c402b5400 Implement HtmlCanvas; move some tests to commonTest 2019-04-09 06:39:38 -05:00
7ba7edb7d4 Set up javascript tests 2019-04-07 16:51:28 -05:00
fe219b5296 Update kotlin gradle plugin; add js target 2019-04-07 13:07:50 -05:00
1abc041d87 Fix ios tests; remove empty test 2019-04-07 09:56:34 -05:00
c16a0ecd65 Migrate Scores to pure Kotlin; display correct score on main screen 2019-04-06 18:53:22 -05:00
6d527a31d7 Add app icon 2019-04-03 06:56:42 -05:00
4d0d631d8a Set minimum number of buttons 2019-04-03 06:56:28 -05:00
5b2b554a7a Dynamically select number of columns to display 2019-04-03 05:50:42 -05:00
33bae657a3 Show numerical habits 2019-04-02 20:16:00 -05:00
082c575f82 Allow user to show/hide archived and completed habits 2019-04-02 07:46:41 -05:00
90f553b4f6 Add Preferences table and repository 2019-04-02 05:57:11 -05:00
6af576c09c Change language automatically 2019-04-01 20:53:00 -05:00
c380abad5a Remove outdated tests 2019-04-01 07:35:18 -05:00
905099ccdb Fix invalid translation files 2019-04-01 07:32:34 -05:00
76b848752c Convert i18n strings from Android XML to Kotlin 2019-04-01 07:22:48 -05:00
48c3ff584a Rename to DetailScreenController; pass habit as argument 2019-03-31 19:43:20 -05:00
979affef22 Replace action sheet icon 2019-03-31 19:08:07 -05:00
274d3d6858 Main screen: add action sheet 2019-03-31 18:08:42 -05:00
f491acdda9 Simplify data trasfer in MainScreenDataSource 2019-03-31 17:31:40 -05:00
6ac7ef7807 Add stubs for Score and Streak 2019-03-31 17:15:39 -05:00
5d1f5168ad Create Observable class 2019-03-31 16:54:43 -05:00
262b9460bd Move README and related files to root 2019-03-31 06:48:30 -05:00
f4e4da6dc5 iOS: use actual data for checkmarks on main screen 2019-03-31 06:48:29 -05:00
024c99e60d Remove Xcode files 2019-03-31 06:48:29 -05:00
aa6b13f3a6 Remove DateCalculator 2019-03-31 06:48:29 -05:00
70a79856f2 Reorganize packages; implement checkmarks 2019-03-31 06:48:29 -05:00
6a30bb98c6 Rename legacy to android 2019-03-31 06:48:29 -05:00
07cf74d400 Remove obsolete react-native version of Android app 2019-03-31 06:48:26 -05:00
94b35545b7 Remove obsolete information from README.md 2019-03-31 06:48:26 -05:00
8544c5dc8a Remove react-native; rewrite main screen in (native) swift 2019-03-31 06:48:26 -05:00
a546f6de73 Update 2019-03-31 06:48:26 -05:00
7cab0a39e5 Implement database access (with migrations) 2019-03-31 06:48:26 -05:00
e19339d808 Add symlink to node_modules 2019-03-31 06:48:26 -05:00
134975b8ba Fix instructions in core/build.gradle 2019-03-31 06:48:26 -05:00
2d0a57efbd Update README 2019-03-31 06:48:26 -05:00
50e94c4d09 Update README; add screenshot 2019-03-31 06:48:26 -05:00
15974bdca8 Add README 2019-03-31 06:48:26 -05:00
0734e74154 Add gradle wrappers 2019-03-31 06:48:26 -05:00
f108bc8dc1 Initial prototype of multi-platform version 2019-03-31 06:48:26 -05:00
4ccda9d6f7 Move existing source code to subfolder 2019-03-31 06:48:26 -05:00
db1ba822fe Merge pull request #472 from chennemann/feat/create-habit-with-long-description
fix: habits with long description could not be saved
2019-01-27 22:30:14 -06:00
bd18a4320a Merge pull request #471 from chennemann/feat/always-hide-archive-action
Always hide archive/unarchive icon
2019-01-27 22:19:43 -06:00
Christoph Hennemann
b180f11834 fix: habits with long description could not be saved
- That was due to a bug where the dialog was not resized
    or scrollable and therefore the save button was not
    accessible

References: #462
2019-01-23 21:16:26 +01:00
Christoph Hennemann
d4b5c7b9d5 feat: Always hide archive/unarchive icon
- Improves usability since the icons do not
    jump around when more than one habit is
    selected

References: #465
2019-01-23 20:55:38 +01:00
a719f6ad98 Merge pull request #459 from chennemann/feat/scroll-bar-in-main-menu
Add scrollbars to habit overview list
2019-01-22 16:49:37 -06:00
Christoph Hennemann
d8894753e0 feat: Add scrollbars to habit overview list
References: #365
2019-01-20 00:40:35 +01:00
178061475e Improve performance when importing database 2018-12-08 12:24:44 -06:00
3581173193 NumberPicker: automatically show keyboard 2018-12-08 08:30:23 -06:00
a839631aae Fix previews in Layout Editor 2018-12-07 05:59:54 -06:00
a3e0d7ffb1 BarCard: fix target 2018-12-06 20:16:45 -06:00
6d4dbcdee7 Disable more flaky tests 2018-12-05 22:08:02 -06:00
33ae289ff1 Temporarily disable flaky tests 2018-12-05 21:52:44 -06:00
fca695ee6b Remove nightly APK badge 2018-12-05 21:27:29 -06:00
8fd175685d Update badge.py to Python 3 2018-12-05 21:02:23 -06:00
6a3e430a5e Merge pull request #438 from vyu1/dev
Add name (Victor Yu) to developer list
2018-09-18 19:04:27 -05:00
Victor Yu
07efa8b321 Add name (Victor Yu) to developer list 2018-09-17 23:31:50 -04:00
9183cb9f37 Update test images 2018-08-14 07:32:02 -05:00
a1bd4836dd gradle: do not abort on lint errors 2018-08-14 07:31:18 -05:00
dc74c0e54b Simplify StackWidgetService; reduce flicker 2018-06-16 19:35:28 -05:00
436d19dfea Merge branch 'dev' into feature/stackview
# Conflicts:
#	uhabits-android/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.kt
2018-06-16 18:06:23 -05:00
6ca4877f1f BarChart: allow user to pick interval 2018-06-15 21:11:47 -05:00
6ad302b697 Show bar chart with monthly totals 2018-06-14 22:59:31 -05:00
b0820095f1 Add action for randomizing habit history (dev mode) 2018-06-13 21:39:06 -05:00
b5fda334d4 build.gradle: Give higher priority to Google Maven repository 2018-06-07 22:57:16 -05:00
88beb7b883 build.gradle: Give higher priority to Google Maven repository 2018-06-07 22:54:07 -05:00
49689317b7 Fix adaptive icons; remove obsolete folder 2018-06-07 22:35:20 -05:00
052d26c708 Upgrade to Kotlin 1.2.41 2018-06-07 22:23:40 -05:00
76c88848b2 Upgrade to Gradle 4.4 2018-06-07 22:03:13 -05:00
87f069f986 Merge branch 'master' into dev 2018-06-04 19:57:02 -05:00
ce27773138 Merge branch 'hotfix/1.7.9' 2018-04-21 10:31:54 -05:00
0864f83307 Update CHANGELOG 2018-04-21 10:17:38 -05:00
624cc67d9b Update translations 2018-04-21 10:17:31 -05:00
462bac8167 Add support for adaptive icons
Closes #395
2018-04-21 09:39:30 -05:00
5865eb41f7 Update notification for Android Oreo
- Add link to notification channel settings
- Remove snooze button

Closes #400
2018-04-21 08:50:53 -05:00
2bfd4a942d Remove unused PebbleReceiverTest 2018-04-21 08:00:00 -05:00
b4a33cba39 Add ActiveAndroid source code to our tree and remove content providers
ActiveAndroid is not actively maintained anymore and contains code
related to that Content Providers that makes the application crash on
Android Oreo.
2018-04-14 10:22:31 -05:00
f02c86e61b Bump targetSdkVersion to 27 2018-04-14 10:19:34 -05:00
5021f50e18 Bump version to 1.7.9 (36) 2018-04-14 08:11:22 -05:00
2654521647 Update only widgets containing the habit that was modified 2018-02-04 10:14:24 -06:00
778a7eb6bc Make widgets dark grey instead of black 2018-02-04 10:13:47 -06:00
71d559d6d9 Merge branch 'dev' into feature/stackview 2018-02-04 09:52:16 -06:00
b94d2f2fa6 StackWidget: allow user to select habits 2018-01-27 20:34:11 -06:00
2904f3e2f8 Move some methods to StringUtils 2018-01-27 20:25:55 -06:00
1ad06bcc15 Preferences: allow storing Long arrays 2018-01-27 19:07:05 -06:00
bf8c14fc03 Fix reminder tests 2017-12-06 22:23:28 -06:00
b46b7aae25 Merge branch 'feature/custom-snooze' into dev 2017-12-06 21:42:04 -06:00
357f51fd5e SnoozeDelayPickerActivity: Fix timepicker layout 2017-12-06 21:41:42 -06:00
7f257e045b MemoryHabitList: iterate over a copy of the list 2017-12-04 23:13:31 -06:00
0dc46d02a4 Minor style changes 2017-11-15 21:54:30 -06:00
ecf3086aef Merge branch 'add-delete-button-to-stats' of git://github.com/derebaba/uhabits into pull/delete-button 2017-11-15 21:42:30 -06:00
derebaba
10be875b48 Fixed test 2017-11-15 10:57:00 +03:00
0077d35ff9 Automatically dismiss summary notification 2017-11-14 23:03:46 -06:00
2a4a7c975f Add action to show reminder right now (for developers) 2017-11-14 22:35:37 -06:00
e91f1c3fa4 Merge branch 'dev' into feature/custom-snooze 2017-11-14 22:20:32 -06:00
9d48b4bcdb Simplify code; change notification actions to Yes/No/Later 2017-11-14 22:07:52 -06:00
7f1a35ebe5 Merge branch 'dev' of git://github.com/rsri/uhabits into pull/cancel_notification 2017-11-14 21:54:16 -06:00
5ccd546958 Merge branch 'dev' of git://github.com/TruffelNL/uhabits into dev
# Conflicts:
#	uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt
2017-11-14 21:45:56 -06:00
Victor Yu
def71d8141 Merge branch 'dev' into stack_checkmark_widget 2017-11-08 21:04:45 -05:00
Victor Yu
8feb07ff1b Code review changes; Made 'stack view' design available for all widgets 2017-11-08 21:03:06 -05:00
derebaba
1edd76ae8c Added delete button to statistics screen 2017-11-02 14:21:27 +03:00
7613e6e1cb Update to Android Studio 3.0 stable 2017-10-29 14:43:17 -05:00
5629a28823 Merge pull request #348 from llunak/fix_synchronized
synchronize HabitList properly
2017-10-26 19:54:51 -05:00
Luboš Luňák
b1c2ab90d3 synchronize HabitList properly
When the app is not running and ReminderReceiver receives ACTION_SHOW_REMINDER,
the (SQLite)HabitList will start loading its data in the background for some
reason, and if the loading takes a moment, ReminderReceiver will call
the unsynchronized HabitList.getById() before the loading finishes,
failing to find the habit for which to show the notification.
2017-10-18 15:17:31 +02:00
Victor Yu
268cb0bc18 Can make either the normal checkmark widget or stackview checkmark widget 2017-10-15 20:44:21 -04:00
Victor Yu
5a78de5a25 Implemented ability to choose multiple habits for stackview 2017-10-15 13:36:54 -04:00
Victor Yu
82972d6e47 Implemented stackwidget style for checkmark widgets 2017-10-15 13:36:54 -04:00
a201273781 Refactoring 2017-10-10 21:31:31 -05:00
819a8d341f Merge branch 'llunak/custom_snooze' into dev 2017-10-09 19:16:57 -05:00
b8f7d4fad2 Add missing test screenshots 2017-10-04 21:43:34 -05:00
dea4e069c5 Add test screenshots for Oreo 2017-10-04 21:27:43 -05:00
07c7234bfc Merge branch 'master' into dev 2017-09-30 13:40:09 -05:00
223b8bc5ec Merge branch 'dev' into llunak/custom_snooze 2017-09-28 07:00:51 -05:00
Luboš Luňák
e052a144bd fix testTruncate_dayOfWeek failing with some locales 2017-09-28 06:55:13 -05:00
Luboš Luňák
318caa886c fix csv export with locales that do not use dot as decimal separator 2017-09-28 06:55:13 -05:00
Luboš Luňák
46c61f9ea9 force US locale for tests 2017-09-28 06:55:13 -05:00
ba78e563cd Merge branch 'keep_position' of git://github.com/llunak/uhabits into dev 2017-09-28 06:44:36 -05:00
acb94db6d6 Introduce failing test for bug #339 2017-09-28 06:40:06 -05:00
Luboš Luňák
d2cc283bd5 don't reset habit position when editing it 2017-09-28 13:03:34 +02:00
6c66078a65 Merge pull request #337 from llunak/export_name
fix name of exported files (correct the time)
2017-09-27 20:32:26 -05:00
Luboš Luňák
be9c2ff64d fix name of exported files (correct the time)
This reverts part of a75a27ad, where this got changed presumably by mistake.
2017-09-27 20:13:29 +02:00
bb22972eb2 Minor code style changes 2017-09-24 07:19:35 -05:00
e911fb35b6 Better explain LED lights feature 2017-09-24 07:16:59 -05:00
d1490ee771 Update Gradle and Android plugin 2017-09-24 06:05:29 -05:00
6d44b4124d Merge pull request #333 from llunak/fix_sql
avoid sql error when updating sql database
2017-09-21 07:01:59 -05:00
Luboš Luňák
f8e0d07236 avoid sql error when updating sql database
The index doesn't exist in version 1.7.6 and so trying to drop it
would lead to an error, resulting in not being able to import
1.7.6 database.
https://github.com/iSoron/uhabits/issues/327
2017-09-16 19:35:50 +02:00
Luboš Luňák
e970473876 make notification snooze popup follow night mode setting 2017-09-15 13:06:42 +02:00
Luboš Luňák
cec05ccbca when asking for snooze delay, first show a list of common options 2017-09-15 13:06:37 +02:00
Luboš Luňák
aac59367dc properly handle the activity window of the snooze time dialog 2017-09-15 12:01:44 +02:00
Luboš Luňák
0421ca0549 make snooze time dialog follow night mode setting 2017-09-15 12:00:51 +02:00
Luboš Luňák
43e802fb8e implement custom snooze for notifications 2017-09-15 12:00:42 +02:00
52c4282601 Merge pull request #329 from llunak/fix_snooze
fix snooze button in notifications
2017-09-13 07:03:46 -05:00
665204bf7a Merge pull request #326 from llunak/notifications-led
Make notifications also blink the LED
2017-09-13 07:00:57 -05:00
Luboš Luňák
f52da56221 fix snooze button in notifications
The "pref_snooze_interval" preference is manipulated by the ListPreference
class, which according to its docs stores the preference as a string.
Without reverting this part of 864636705d,
this results in "java.lang.ClassCastException: java.lang.String cannot be
cast to java.lang.Long" when trying to snooze a notification.
2017-09-12 15:54:43 +02:00
Luboš Luňák
4d59783809 make LED blinking for notifications configurable 2017-09-11 15:18:42 +02:00
Luboš Luňák
a680d57cac make notifications also blink the LED 2017-09-11 13:46:49 +02:00
TruffelNL
f6620be2d9 Removed an if statement
Removed the check if a summary was already shown.
2017-08-15 23:44:41 +02:00
TruffelNL
1c2abb543b Added private variables 2017-08-15 23:09:19 +02:00
TruffelNL
87cf2871a7 Added bundled notifications
Added bundled/grouped/stacked notifications as per https://github.com/iSoron/uhabits/issues/243
2017-08-15 23:02:58 +02:00
srikanth r
e041d9041b Implemented cancel to reminders of habits. 2017-08-04 23:01:41 +05:30
dccf5eae47 Only use signing configuration if env variables are defined 2017-08-02 11:19:25 -04:00
b2a8c9c45f Remove PebbleReceiver 2017-07-26 11:39:02 -04:00
125a574ff9 Merge branch 'master' into dev 2017-07-26 10:37:08 -04:00
a75a27ad42 Make the day start at 3am instead of midnight
Closes #50
2017-07-25 20:18:01 -04:00
4126f01ef1 Reactivate proguard 2017-07-25 18:09:48 -04:00
9816fc9127 Update Android plugin 2017-07-25 12:35:22 -04:00
b135aa09a3 Use more simple toString style 2017-07-24 12:14:48 -04:00
f3a64fd67a Add tests for equals, hashCode and toString 2017-07-24 11:41:12 -04:00
864636705d Implement tests for Preferences 2017-07-24 09:54:51 -04:00
281861cac5 build.sh: read keystore from environment variables 2017-07-24 00:14:46 -04:00
c05f50998f Add branch and commit hash to version name 2017-07-24 00:14:46 -04:00
b2734b179c README: Add link to latest stable release 2017-07-23 23:30:24 -04:00
00a4abf266 README: Switch to shields.io 2017-07-23 22:17:35 -04:00
47e279b3b3 Update SharedPreferencesStorage 2017-07-23 20:14:12 -04:00
87f1d635d8 Implement PropertiesStorage 2017-07-23 20:07:44 -04:00
fdcb9daadc LoopDBImporter: update table names 2017-07-23 17:26:08 -04:00
5fde0501b5 README: Add link to dev apk 2017-07-23 17:04:20 -04:00
f72799f48c README: Update badge link 2017-07-23 14:26:34 -04:00
60c62b8609 Temporarily disable proguard 2017-07-23 13:50:33 -04:00
e5bc06c138 Increase time limit 2017-07-23 13:37:49 -04:00
fa5ba0c1ef Update build.sh 2017-07-23 13:00:57 -04:00
43be70b27c build.sh: Do not abort when uninstall fails 2017-07-23 10:59:18 -04:00
89400e281e Move version numbers from build.gradle to gradle.properties 2017-07-23 10:03:09 -04:00
57dc19550d Fix errors 2017-07-22 20:05:01 -04:00
a8aa6f192c Replace Long by Timestamp 2017-07-22 16:29:08 -04:00
882ddba324 Fix acceptance tests 2017-07-22 09:28:30 -04:00
2f7509b94e Update test images (KitKat) 2017-07-21 19:31:37 -04:00
10e68aa008 Update build.sh 2017-07-21 19:30:55 -04:00
a5720e8d7f BaseViewTest: increase tolerance 2017-07-21 19:30:46 -04:00
c7aaa98935 Convert widget providers to Kotlin 2017-07-21 18:32:13 -04:00
efcb5710c0 Convert more classes to Kotlin 2017-07-21 18:32:03 -04:00
3783fd8506 Convert RingtoneManager to Kotlin 2017-07-21 18:31:49 -04:00
6f80a9c030 Convert intents and notifications to Kotlin 2017-07-21 18:17:42 -04:00
a02376497a Convert automation and database packages to Kotlin 2017-07-21 18:17:28 -04:00
180c18f6bf Merge branch 'feature/kotlin' into dev 2017-07-21 18:12:45 -04:00
2db4c06fe8 Merge branch 'master' into dev
# Conflicts:
#	app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java
#	app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java
#	app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java
#	app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java
#	app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java
#	app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java
#	app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java
#	uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java
#	uhabits-android/src/main/res/values-id/strings.xml
#	uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java
2017-07-18 22:54:52 -04:00
122b300c50 Update view test images (KitKat) 2017-07-18 16:10:41 -04:00
0984f7ff5d Update Android gradle plugin 2017-07-18 06:57:22 -04:00
2d9a5ae7e2 Update links to build server 2017-07-17 22:01:37 -04:00
Jude Ibe (Toochukwu)
02f9660fda Socket.io depreciated function
Kept socket.io to previous version in gradle.
2017-07-14 10:20:51 -05:00
Jude Ibe (Toochukwu)
d3d733ab42 Build Tools Upgrade
3.0.0-alpha4 to 3.0.0-alpha6
Gradle 4
2017-07-14 10:19:56 -05:00
bb282da92d Fix tests 2017-06-23 16:25:25 -04:00
38d3b0d047 Update habit.position after reordering list 2017-06-23 12:12:23 -04:00
8ccada67d6 Import Loop DB without app restart 2017-06-23 10:24:54 -04:00
33f7acc9ca Add reordering tests for SQLiteHabitList 2017-06-23 09:29:19 -04:00
5d9563b9d8 Fix handling of null values in AndroidDatabase 2017-06-23 09:14:36 -04:00
f55dc0d811 Fix animation when card moves and updates simultaneously 2017-06-22 23:31:46 -04:00
7872983064 Fix order by position 2017-06-22 23:21:44 -04:00
ea640a8a17 Rebuild order after removing habit 2017-06-22 22:50:27 -04:00
6801d1d1ae Add more constraints on table Repetitions 2017-06-21 13:18:29 -04:00
3584affbe0 Move BaseUnitTest 2017-06-21 10:20:59 -04:00
1069fcfc62 Move importers to uhabits-core 2017-06-21 00:30:32 -04:00
59745fb90f Refactor Android database classes 2017-06-20 22:37:35 -04:00
1976160ae8 Move tests to uhabits-core 2017-06-20 22:20:46 -04:00
6dd7e49112 Move database migrations to uhabits-core 2017-06-20 21:46:30 -04:00
ecb5352134 Implement JDBC database 2017-06-20 14:13:37 -04:00
b96385c4a7 Move models.sqlite to uhabits-core 2017-06-20 12:50:57 -04:00
96c1a046d4 Merge branch 'feature/raw-sqlite' into dev 2017-06-20 10:43:04 -04:00
00660d3e36 Make filtered MemoryHabitLists update automatically 2017-06-20 10:34:21 -04:00
PrototypeNM1
b14ca5c625 Don't list archived Habits when creating Widgets. (#309)
Fixes #283
2017-06-20 06:07:15 -04:00
71fe6137be Remove old database migrations; add missing ones 2017-06-19 12:08:06 -04:00
0a5d565030 Remove dependency: ActiveAndroid 2017-06-19 11:12:31 -04:00
6d06e06840 Simplify SQLite lists 2017-06-19 09:23:40 -04:00
edeba897fb Implement SQLiteRepository 2017-06-19 09:22:48 -04:00
e4b9c50ee2 Update Gradle plugin 2017-06-19 09:22:30 -04:00
af7c4e227d Fix merge 2017-06-10 19:34:44 -04:00
96ab887545 Merge tag 'v1.7.5' into dev
1.7.5
2017-06-10 18:59:28 -04:00
f8a9da59dd Simplify ListHabitsActivity 2017-06-05 22:56:08 -04:00
50a6c6d9dd Remove unused code 2017-06-05 18:11:30 -04:00
8d181a6683 Simplify ShowHabitActivity 2017-06-05 18:01:13 -04:00
217516ad59 Create android-pickers module 2017-06-05 16:38:58 -04:00
fc4b610d59 Create android-base module 2017-06-05 15:58:42 -04:00
382b52e5b2 Merge tag 'v1.7.4' into dev
1.7.4

# Conflicts:
#	app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java
#	app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java
#	uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java
2017-06-05 09:20:47 -04:00
277738f94d Fix default color and failing tests 2017-06-04 13:14:45 -04:00
cf25229fbc Merge branch 'dev' into pull/austil 2017-06-04 12:59:15 -04:00
7652d71c94 Uninstall test APK before running tests 2017-06-04 12:46:30 -04:00
74e0dcf706 Disable PebbleReceiver tests on KitKat 2017-06-04 12:46:16 -04:00
4e1cc6dc80 Fix tests for KitKat 2017-06-04 12:21:29 -04:00
c34f9f9e9f Merge branch 'dev' of https://github.com/austil/uhabits into pull/austil 2017-06-04 11:30:10 -04:00
ed9066f393 Implement alternative checkmark algorithm
Fix failing tests
2017-06-04 10:58:00 -04:00
96e1771c25 Remove inferred tables from SQLite database 2017-06-03 10:51:17 -04:00
austil
d0d3c7eef5 More Colors - requested changes 2017-06-03 15:16:09 +02:00
6875fc0428 Move notifications and reminders to uhabits-core 2017-06-02 19:30:39 -04:00
b88b3a683d Move preferences to uhabits-core 2017-06-02 18:24:12 -04:00
d97f94075d Implement widget test 2017-06-02 15:25:24 -04:00
28f095e56a Implement additional UI tests 2017-06-02 14:45:06 -04:00
ead87519b1 UI tests: reset state programmatically 2017-06-02 13:16:15 -04:00
7cfe3355e4 Downgrade Mockito to 1.0 on AndroidTest 2017-06-01 21:55:13 -04:00
a51ecaaf24 Annotate all Android tests 2017-06-01 21:55:13 -04:00
322645da9b Update badge link 2017-05-31 21:35:07 -04:00
5eb63df633 Merge branch 'modules' into dev 2017-05-31 21:17:28 -04:00
e4b5a3ea45 Implement acceptance tests for some basic features 2017-05-31 18:24:22 -04:00
88c1e73720 Merge tag 'v1.7.3' into dev
1.7.3
2017-05-30 11:11:42 -04:00
0393e58d3b Merge tag 'v1.7.3' into dev
1.7.3
2017-05-30 10:38:07 -04:00
6ccfb53329 Add missing tests for RepetitionList and Habit 2017-05-30 08:19:28 -04:00
8a29fbf07d Merge branch 'hotfix/1.7.3' into modules
# Conflicts:
#	uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java
#	uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java
#	uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java
2017-05-29 22:55:08 -04:00
223aee3be2 Add Test Butler APK 2017-05-29 09:14:02 -04:00
745d07024c Remove unused permissions 2017-05-29 08:37:49 -04:00
94025c5262 Add test for BundleSavedState 2017-05-28 22:26:36 -04:00
ab09eb8a03 Update CI build script 2017-05-28 17:04:04 -04:00
e826c80ff2 Fix Android tests 2017-05-28 15:26:28 -04:00
6255fe2d12 Make generated classes work on Android Studio 2017-05-28 11:39:07 -04:00
1746920699 Rename uhabits-core package 2017-05-28 11:38:20 -04:00
c9b62669de Merge branch 'dev' into modules 2017-05-28 09:44:55 -04:00
d2f367678f Merge tag 'v1.7.2' into dev
v1.7.2
2017-05-28 00:47:08 -04:00
56c5fb6c9d Write missing tests 2017-05-27 22:46:33 -04:00
d8d4c4f55e Write tests for ListHabits behaviors 2017-05-27 20:33:30 -04:00
70423ddb0a Move ListHabits controllers to uhabits-core 2017-05-27 18:52:06 -04:00
94c70485b7 Remove unit tests for uhabits-android 2017-05-27 10:31:05 -04:00
3e558be4d4 Move ListHabitsBehavior to uhabits-core 2017-05-26 22:04:59 -04:00
95385fa8f4 Activate coverage report for uhabits-core 2017-05-26 18:38:08 -04:00
fa4944700c Move UI behavior to uhabits-core 2017-05-26 16:33:21 -04:00
df0cf57984 Move tests to androidTest 2017-05-25 18:41:59 -04:00
29d1de46e7 Reorganize tests 2017-05-25 15:59:03 -04:00
cb4ab3b436 Move ExportCSVTask to uhabits-core 2017-05-25 12:50:38 -04:00
370e7343d7 Move commands to uhabits-core 2017-05-25 10:00:56 -04:00
acd653db70 Create org.isoron.androidbase package 2017-05-25 09:31:54 -04:00
51ca4aa98e Move remaining model classes 2017-05-25 08:54:21 -04:00
d23b59ced2 Move some models 2017-05-25 08:24:22 -04:00
f18ac9db48 Create module uhabits-core 2017-05-25 08:16:55 -04:00
c20ca3921f Rename android module to uhabits-android 2017-05-25 08:00:00 -04:00
96f620455f Update toolchain 2017-05-25 00:02:02 -04:00
2dd14dbf04 Small changes to coverage badge 2017-05-24 17:57:28 -04:00
8346f28497 Generate code coverage badges 2017-05-24 17:33:25 -04:00
97967b2b2e Improve build script 2017-05-24 00:23:01 -04:00
cf6a257143 Activate ProGuard; test signed release APK instead of debug 2017-05-23 19:13:30 -04:00
275125d230 Update dependencies 2017-05-23 16:25:11 -04:00
cb7b569d4e Use 'adb emu kill' to stop emulator 2017-05-23 12:21:53 -04:00
89b6f4c5cc Fix compilation error when ROBO_SDK is defined 2017-05-23 11:58:29 -04:00
8d555eb837 Read Robolectric version from environment 2017-05-23 11:39:17 -04:00
9288528f94 Fix assets path 2017-05-23 11:07:12 -04:00
e92ffb3894 Drop support for Android ICS and Jelly Bean; remove obsolete code 2017-05-23 10:50:11 -04:00
96bdb42365 Remove unused files 2017-05-23 00:18:22 -04:00
ecb207d322 Generate XML reports for instrumented tests 2017-05-22 22:37:08 -04:00
64001604cf Update link to build server 2017-05-22 14:24:22 -04:00
b9d3d22894 Use absolute paths on run_tests 2017-05-22 13:22:16 -04:00
25705297cb Update run scripts 2017-05-21 21:12:08 -04:00
532637ef7e Update link to CI server 2017-05-21 13:14:26 -04:00
0a1907ee2c Merge tag 'v1.7.1' into dev
v1.7.1
2017-05-21 10:20:58 -04:00
d0e76d3d55 Implement tests for BaseScreen 2017-05-20 23:36:07 -04:00
1832ea639b Implement tests for BaseActivity 2017-05-20 22:10:37 -04:00
cd3944b90f Add Robolectric dependency; implement a basic test 2017-05-20 20:05:12 -04:00
9f2f8f7117 View tests: create wrapper for getDimension; other fixes 2017-05-17 22:41:06 -04:00
1b97b9040d View tests: use fixed screen density 2017-05-17 19:20:44 -04:00
austil
8a80a66a80 More Colors for habits 2017-05-06 16:38:14 +02:00
austil
894423e49f Revert "New color but Sort By Color broken"
This reverts commit 4ce9013e6a.
2017-05-06 13:27:28 +02:00
austil
4ce9013e6a New color but Sort By Color broken 2017-05-06 13:25:17 +02:00
96b95edef8 Separate expected view snapshots according to version 2017-04-22 23:12:44 -04:00
704854fdf1 View tests: adjust similarity cutoff 2017-04-22 22:01:06 -04:00
6e0393f611 Improve view tests 2017-04-22 00:10:42 -04:00
e6ceed9ec9 Update README.md 2017-04-21 20:13:42 -04:00
cf3d289145 Enable XML reports 2017-04-21 19:19:42 -04:00
6bd59aad97 Update code coverage gradle command 2017-04-21 18:53:17 -04:00
a5567d491f Enable test coverage 2017-04-21 18:26:29 -04:00
9604c26973 Remove inactive espresso tests; fix other tests 2017-04-21 12:03:55 -04:00
81ad1ba8c8 Merge branch 'feature/parallel' into dev 2017-04-21 10:08:06 -04:00
1d68122a6f Replace layout inflation by code 2017-04-20 18:14:06 -04:00
7d9d45ffed Small performance changes 2017-04-20 16:39:41 -04:00
50e21b2cc1 Merge branch 'hotfix/1.7.1' into dev 2017-04-19 19:47:49 -04:00
fb1b1221d3 Merge remote-tracking branch 'origin/dev' into dev 2017-04-15 18:10:08 -04:00
788c790f9e Detect network changes 2017-04-15 17:13:41 -04:00
293d838d80 Move Sync to foreground service 2017-04-15 16:34:15 -04:00
3da996b8a4 Update SyncManager 2017-04-14 23:08:00 -04:00
08e3c9cc40 Replace toJSON methods by Gson 2017-04-14 20:32:00 -04:00
13b4128777 Update code coverage 2017-04-14 10:56:38 -04:00
b4e79c3f4b Merge branch 'feature/sync' into dev 2017-04-13 23:34:57 -04:00
2d07f5e924 Create switch for numerical habits feature 2017-04-13 21:44:23 -04:00
Mihail Stefanov
cb29fef17e Use of Cyrillic letters to display Bulgarian (#276)
I switched the phonetic notation with Cyrillic letters to denote the Bulgarian language.
2017-04-13 19:08:33 -04:00
1d76760dc8 Create hidden settings for experimental features 2017-04-12 23:19:22 -04:00
b6994034d2 Fix failing test 2017-04-12 13:09:28 -04:00
8fc0c072e5 Merge branch 'artem-p-reminderDaysFix' into dev 2017-04-12 11:25:33 -04:00
e6deb1f281 Merge branch 'hotfix/1.7.1' into dev 2017-04-12 11:25:28 -04:00
artem-p
bb0d43018e Fix resetting reminder days 2017-04-12 11:17:27 -04:00
Dmitriy Bogdanov
814b734ad3 Import java.text.DecimalFormat instead of one from ICU (#273) 2017-04-10 08:05:08 -04:00
6f5941472b Merge remote-tracking branch 'origin/dev' into dev 2017-04-09 18:15:55 -04:00
7699423aa7 Merge tag 'v1.7.0' into dev
1.7.0
2017-04-09 18:14:53 -04:00
e48dab9ed3 Update translation link 2017-04-04 17:47:14 -04:00
53a599c6b8 Merge branch 'feature/numerical-habits' into dev 2017-03-30 23:00:12 -04:00
103c0b57f8 Unify habit creation dialogs 2017-03-28 16:59:08 -04:00
7de69c1c10 Simplify BooleanHabitDialog subclasses and factories 2017-03-28 13:36:49 -04:00
fe7e8ef039 Refactor habit creation dialogs 2017-03-28 12:06:09 -04:00
19f4a19dba Minor changes to BarChart 2017-03-27 19:25:53 -04:00
b66da24e39 Fix the form fields containing examples 2017-03-27 18:41:52 -04:00
3dd33274e4 Small changes to formatValue 2017-03-27 17:50:57 -04:00
c61834e604 Color days on HistoryChart according to target 2017-03-26 16:36:28 -04:00
e082705b0c First version of BarChart 2017-03-26 16:16:13 -04:00
83ce92d8ac Allow fractional values to be entered 2017-03-26 13:19:01 -04:00
177525817c Small changes to the layout of NumberButtonView 2017-03-26 10:57:04 -04:00
1dfa0c6b0d Hide completed according to the target value 2017-03-26 10:32:27 -04:00
b3f039d658 Make score take target value into account 2017-03-26 10:14:23 -04:00
7d2e8573f8 Persist unit and target value of a habit 2017-03-25 19:57:38 -04:00
5653651c0d Update score calculation
The value of a score is now a double. For boolean habits, this number goes from zero
to one and corresponds to the percentage. For numerical habits, it now corresponds
to a weighted average of the checkmark values. Also, for non-daily boolean habits, the
score now increases with implicit checkmarks.
2017-03-25 12:31:19 -04:00
d3b540199c Switch score values from int to double 2017-03-25 10:46:35 -04:00
f0430ffeb3 Use alternative design with units on NumberButtonView 2017-03-25 09:43:52 -04:00
d03edf2895 Update habit creation dialogs 2017-03-24 16:47:40 -04:00
5c1ccfe6fe Fix small issues with the number picker 2017-03-24 10:13:19 -04:00
5b9e90fe7a Persist repetition values 2017-03-24 09:41:52 -04:00
ac32460859 Create number picker dialog 2017-03-24 08:20:59 -04:00
e2a8de3acf Update controllers and HabitCardView 2017-03-23 19:09:44 -04:00
7bf9f88ee3 Implement NumberButtonView and related classes 2017-03-23 18:26:58 -04:00
c20d5c8729 Make model classes thread-safe 2016-11-24 20:06:28 -05:00
e6e80b9841 Update sync protocol 2016-05-22 18:28:06 -04:00
442c3ed78d Merge branch 'dev' into feature/sync 2016-05-22 17:50:14 -04:00
2399dccddc Emit JSON object instead of string 2016-05-19 10:35:44 -04:00
dd5f37290c Merge branch 'dev' into feature/sync 2016-05-17 10:58:53 -04:00
5b402478e9 Use encrypted connection to server (TLS) 2016-05-16 22:29:10 -04:00
e3b7e9f60f Persist pending events to database 2016-05-16 08:20:08 -04:00
b0040bd83c Fetch commands since last sync 2016-05-15 09:06:25 -04:00
56e1268f85 Sync remaining commands 2016-05-15 09:06:25 -04:00
41d9e2f0f5 Sync archive/unarchive commands 2016-05-15 09:06:25 -04:00
1fcfb9b22e First version of sync feature 2016-05-15 09:06:25 -04:00
1327 changed files with 53631 additions and 27523 deletions

50
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Build & Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out source code
uses: actions/checkout@v1
- name: Install Java Development Kit 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build APK & Run small tests
run: android/build.sh build
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: debug-apk
path: android/build/*apk
- name: Upload build outputs
uses: actions/upload-artifact@v2
with:
name: build
path: android/uhabits-android/build/outputs/
test:
needs: build
runs-on: macOS-latest
strategy:
matrix:
api-level: [23, 24, 25, 26, 27, 28, 29]
steps:
- name: Check out source code
uses: actions/checkout@v1
- name: Download previous build folder
uses: actions/download-artifact@v2
with:
name: build
path: android/uhabits-android/build/outputs/
- name: Run medium tests
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
script: android/build.sh medium-tests

42
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Build, Test & Publish
on:
push:
branches:
- master
jobs:
build:
runs-on: macOS-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- uses: actions/checkout@v1
- name: Install GPG
uses: olafurpg/setup-gpg@v2
- name: Decrypt secrets
env:
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
run: .secret/decrypt.sh
- name: Install Java Development Kit 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build APK & Run small tests
env:
RELEASE: 1
run: android/build.sh build
- name: Run medium tests
uses: ReactiveCircus/android-emulator-runner@v2.2.0
env:
RELEASE: 1
with:
api-level: 29
script: android/build.sh medium-tests
- name: Upload build to GitHub
uses: actions/upload-artifact@v1
with:
name: Build
path: android/uhabits-android/build/outputs/
- name: Upload APK to Google Play
run: cd android && ./gradlew publishReleaseApk

34
.gitignore vendored
View File

@@ -1,25 +1,23 @@
*.ap_
*.apk
*.class
*.dex
*.iml
*.local.*
*.pbxuser
*.perspective
*.perspectivev3
*.swp
*.trace
*~
*~.nib
*.hprof
.DS_Store
.classpath
._.DS_Store
.externalNativeBuild
.gradle
.idea
.project
Thumbs.db
art/
bin/
.secret
build
build/
captures/
docs/
gen/
captures
local.properties
crowdin.yaml
local
secret/
node_modules
*xcuserdata*
*.sketch
/design
/releases
/screenshots

16
.secret/decrypt.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
cd "$(dirname "$0")"
if [ -z "$GPG_PASSWORD" ]; then
echo Env variable GPG_PASSWORD must be defined
exit 1
fi
gpg \
--quiet \
--batch \
--yes \
--decrypt \
--passphrase="$GPG_PASSWORD" \
--output secret.tar.gz \
secret
tar -xzf secret.tar.gz
rm secret.tar.gz

BIN
.secret/secret Normal file

Binary file not shown.

View File

@@ -1,5 +1,67 @@
# Changelog
### 2.0.0 (TBD)
* **New Features:**
* Track numerical habits (@iSoron, @namnl)
* Skip days without breaking streak (@KristianTashkov)
* Sort habits by status (@hiqua)
* Sort habits in reverse order (@iSoron)
* Add notes to habits (@recheej)
* Improve readibility of charts (@chennemann)
* Delay new day until 3am (@KristianTashkov)
* Export backups daily (@iSoron)
* **Bug fixes:**
* Reset chart offset when switching scale (@alxmjo)
* Don't show reminders from archived habits (@KristianTashkov)
* Lapses on non-daily habits decrease the score too much (@iSoron)
* Update widgets at midnight (@KristianTashkov)
* **Refactoring:**
* Convert files to Kotlin (@olegivo)
### 1.8.10 (Nov 26, 2020)
* Update translations
### 1.8.9 (Nov 18, 2020)
* Manage exceptions when activities don't exist to handle intents (#181)
* MemoryHabitList: Inherit parent's order (#598)
* Remove notification groups; revert to default system behavior
* Remove SyncManager and Internet permission
### 1.8.8 (June 21, 2020)
* Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably.
* Fix crash when saving habit
### 1.8.0 (Jan 1, 2020)
* New bar chart showing number of repetitions performed in each week, month, quarter or year.
* Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will no longer break your streak.
* Many more colors to choose from (now 20 in total).
* Ability to customize how transparent the widgets are on your home screen.
* Ability to customize the first day of the week.
* Yes/No buttons on notifications, instead of just "Check".
* Automatic dark theme according to phone settings (Android 10).
* Smaller APK and backup files.
* Many other internal code changes improving performance and stability.
### 1.7.11 (Aug 10, 2019)
* Fix bug that produced corrupted CSV files in some countries
### 1.7.10 (June 15, 2019)
* Fix bug that prevented some devices from showing notifications.
* Update targetSdk to Android Pie (API level 28)
### 1.7.8 (April 21, 2018)
* Add support for adaptive icons (Oreo)
* Add support for notification channels (Oreo)
* Update translations
### 1.7.7 (September 30, 2017)
* Fix bug that caused reminders to show repeatedly on DST changes

114
README.md
View File

@@ -1,22 +1,20 @@
<a href="https://github.com/iSoron/uhabits/actions?query=workflow%3A%22Build+%26+Test%22">
<img src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
</a>
<a href="https://github.com/iSoron/uhabits/releases">
<img src="https://img.shields.io/github/v/release/iSoron/uhabits" />
</a>
# 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,
Loop is a mobile 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>
<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="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
</p>
## Screenshots
@@ -30,34 +28,32 @@ source.
## Features
* **Simple, beautiful and modern interface.** Loop has a minimalistic interface
that is easy to use and follows the material design guidelines.
* <b>Beautiful, minimalistic and lightweight interface.</b>
Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones.
* **Habit score.** In addition to showing your current streak, Loop has an
advanced algorithm for calculating the strength of your habits. Every
repetition makes your habit stronger, and every missed day makes it weaker. A
few missed days after a long streak, however, will not completely destroy
your entire progress.
* <b>Habit score.</b>
Loop has an advanced formula 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 progress, unlike many other don't-break-the-chain apps.
* **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.
* <b>Flexible schedules.</b>
In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
* **Flexible schedules.** Supports both daily habits and habits with more
complex schedules, such as 3 times every week; one time every other week; or
every other day.
* <b>Reminders.</b>
Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification.
* **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.
* <b>Widgets.</b>
Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app.
* **Optimized for smartwatches.** Reminders can be checked, snoozed or
dismissed directly from your Android Wear watch.
* <b>Take control of your data.</b>
If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, checkmarks can be added through other apps, such as Tasker.
* **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.
* <b>No limitations.</b>
Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users. There are no in-app purchases.
* <b>Completely ad-free and open source.</b>
There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3).
* <b>Works offline and respects your privacy.</b>
Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it.
## Installing
@@ -84,7 +80,7 @@ contribute, even if you are not a software developer.
* **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
please join our [open translation project][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
@@ -94,34 +90,34 @@ contribute, even if you are not a software developer.
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
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 free software: you can redistribute it and/or modify
it under the terms of the 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.
Loop Habit Tracker is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
[screen1]: screenshots/original/uhabits1.png
[screen2]: screenshots/original/uhabits2.png
[screen3]: screenshots/original/uhabits3.png
[screen4]: screenshots/original/uhabits4.png
[screen5]: screenshots/original/uhabits5.png
[screen6]: screenshots/original/uhabits6.png
[screen1th]: screenshots/thumbs/uhabits1.png
[screen2th]: screenshots/thumbs/uhabits2.png
[screen3th]: screenshots/thumbs/uhabits3.png
[screen4th]: screenshots/thumbs/uhabits4.png
[screen5th]: screenshots/thumbs/uhabits5.png
[screen6th]: screenshots/thumbs/uhabits6.png
[poedit]: https://poeditor.com/join/project/8DWX5pfjS0
[screen1]: screenshots/uhabits1.png
[screen2]: screenshots/uhabits2.png
[screen3]: screenshots/uhabits3.png
[screen4]: screenshots/uhabits4.png
[screen5]: screenshots/uhabits5.png
[screen6]: screenshots/uhabits6.png
[screen1th]: screenshots/uhabits1_th.png
[screen2th]: screenshots/uhabits2_th.png
[screen3th]: screenshots/uhabits3_th.png
[screen4th]: screenshots/uhabits4_th.png
[screen5th]: screenshots/uhabits5_th.png
[screen6th]: screenshots/uhabits6_th.png
[poedit]: http://translate.loophabits.org
[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

29
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
*.ap_
*.apk
*.class
*.dex
*.iml
*.local
*.local.*
*.swp
*.trace
*~
.DS_Store
.classpath
.gradle
.idea
.project
.secret
Thumbs.db
art/
bin/
build/
captures/
docs/
gen/
local.properties
crowdin.yaml
crowdin.yml
local
tmp/
secret/

1
android/android-base/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,33 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion COMPILE_SDK_VERSION as Integer
defaultConfig {
minSdkVersion MIN_SDK_VERSION as Integer
targetSdkVersion TARGET_SDK_VERSION as Integer
buildConfigField 'int', 'VERSION_CODE', "$VERSION_CODE"
buildConfigField 'String', 'VERSION_NAME', "\"$VERSION_NAME\""
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
dependencies {
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation "org.apache.commons:commons-lang3:3.5"
annotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
}

25
android/android-base/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest package="org.isoron.androidbase"
xmlns:android="http://schemas.android.com/apk/res/android"/>

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 Á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.androidbase
import android.content.Context
import android.os.Build
import android.os.Environment
import android.view.WindowManager
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
open class AndroidBugReporter @Inject constructor(@AppContext private val context: Context) {
/**
* Captures and returns a bug report. The bug report contains some device
* information and the logcat.
*
* @return a String containing the bug report.
* @throws IOException when any I/O error occur.
*/
@Throws(IOException::class)
fun getBugReport(): String {
var log = "---------- BUG REPORT BEGINS ----------\n"
log += "${getLogcat()}\n"
log += "${getDeviceInfo()}\n"
log += "---------- BUG REPORT ENDS ------------\n"
return log
}
@Throws(IOException::class)
fun getLogcat(): String {
val maxLineCount = 250
val builder = StringBuilder()
val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d"))
val inputReader = InputStreamReader(process.inputStream)
val bufferedReader = BufferedReader(inputReader)
val log = LinkedList<String>()
var line: String?
while (true) {
line = bufferedReader.readLine()
if (line == null) break;
log.addLast(line)
if (log.size > maxLineCount) log.removeFirst()
}
for (l in log) {
builder.appendln(l)
}
return builder.toString()
}
/**
* Captures a bug report and saves it to a file in the SD card.
*
* The contents of the file are generated by the method [ ][.getBugReport]. The file is saved
* in the apps's external private storage.
*
* @return the generated file.
* @throws IOException when I/O errors occur.
*/
fun dumpBugReportToFile() {
try {
val date = SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US).format(Date())
val dir = AndroidDirFinder(context).getFilesDir("Logs")
?: throw IOException("log dir should not be null")
val logFile = File(String.format("%s/Log %s.txt", dir.path, date))
val output = FileWriter(logFile)
output.write(getBugReport())
output.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun getDeviceInfo(): String {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return buildString {
appendln("App Version Name: ${BuildConfig.VERSION_NAME}")
appendln("App Version Code: ${BuildConfig.VERSION_CODE}")
appendln("OS Version: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})")
appendln("OS API Level: ${Build.VERSION.SDK_INT}")
appendln("Device: ${Build.DEVICE}")
appendln("Model (Product): ${Build.MODEL} (${Build.PRODUCT})")
appendln("Manufacturer: ${Build.MANUFACTURER}")
appendln("Other tags: ${Build.TAGS}")
appendln("Screen Width: ${wm.defaultDisplay.width}")
appendln("Screen Height: ${wm.defaultDisplay.height}")
appendln("External storage state: ${Environment.getExternalStorageState()}")
appendln()
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2017 Á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.androidbase
import android.content.Context
import androidx.core.content.ContextCompat
import org.isoron.androidbase.utils.FileUtils
import java.io.File
import javax.inject.Inject
class AndroidDirFinder @Inject constructor(@param:AppContext private val context: Context) {
fun getFilesDir(relativePath: String): File? {
return FileUtils.getDir(
ContextCompat.getExternalFilesDirs(context, null),
relativePath
)
}
}

View File

@@ -16,9 +16,11 @@
* 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.androidbase
package org.isoron.uhabits.models.sqlite;
import javax.inject.Qualifier
public class InvalidDatabaseVersionException extends RuntimeException
{
}
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class AppContext

View File

@@ -17,18 +17,18 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
package org.isoron.androidbase;
import android.content.*;
import dagger.*;
@Module
public class AppModule
public class AppContextModule
{
private final Context context;
public AppModule(@AppContext Context context)
public AppContextModule(@AppContext Context context)
{
this.context = context;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2017 Á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.androidbase
import org.isoron.androidbase.activities.BaseActivity
class BaseExceptionHandler(private val activity: BaseActivity) : Thread.UncaughtExceptionHandler {
private val originalHandler: Thread.UncaughtExceptionHandler? =
Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(thread: Thread?, ex: Throwable?) {
if (ex == null) return
if (thread == null) return
try {
ex.printStackTrace()
AndroidBugReporter(activity).dumpBugReportToFile()
} catch (e: Exception) {
e.printStackTrace()
}
originalHandler?.uncaughtException(thread, ex)
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2017 Á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.androidbase
import android.content.Context
import java.security.KeyStore
import java.security.cert.CertificateFactory
import javax.inject.Inject
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
class SSLContextProvider @Inject constructor(@param:AppContext private val context: Context) {
fun getCACertSSLContext(): SSLContext {
try {
val cf = CertificateFactory.getInstance("X.509")
val ca = cf.generateCertificate(context.assets.open("cacert.pem"))
val ks = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
val alg = TrustManagerFactory.getDefaultAlgorithm()
val tmf = TrustManagerFactory.getInstance(alg).apply {
init(ks)
}
return SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

View File

@@ -16,16 +16,11 @@
* 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.androidbase.activities
package org.isoron.uhabits.activities;
import java.lang.annotation.*;
import javax.inject.*;
import javax.inject.*
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityContext
{
}
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ActivityContext

View File

@@ -17,20 +17,26 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
package org.isoron.androidbase.activities;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.tasks.*;
import android.content.*;
import dagger.*;
@AppScope
@Component(modules = {
AppModule.class, SingleThreadTaskRunner.class, SQLModelFactory.class
})
public interface AndroidTestComponent extends AppComponent
@Module
public class ActivityContextModule
{
private Context context;
public ActivityContextModule(Context context)
{
this.context = context;
}
@Provides
@ActivityContext
public Context getContext()
{
return context;
}
}

View File

@@ -16,13 +16,12 @@
* 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.androidbase.activities
package org.isoron.uhabits.activities;
import javax.inject.*;
import javax.inject.*
/**
* Scope used by objects that live as long as the activity is alive.
*/
@Scope
public @interface ActivityScope { }
annotation class ActivityScope

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.androidbase.activities
import android.R.anim
import android.content.*
import android.os.*
import android.view.*
import androidx.appcompat.app.*
import org.isoron.androidbase.*
/**
* Base class for all activities in the application.
*
* This class delegates the responsibilities of an Android activity to other classes. For example,
* callbacks related to menus are forwarded to a []BaseMenu], while callbacks related to activity
* results are forwarded to a [BaseScreen].
*
*
* A BaseActivity also installs an [java.lang.Thread.UncaughtExceptionHandler] to the main thread.
* By default, this handler is an instance of BaseExceptionHandler, which logs the exception to the
* disk before the application crashes. To the default handler, you should override the method
* getExceptionHandler.
*/
abstract class BaseActivity : AppCompatActivity() {
private var baseMenu: BaseMenu? = null
private var screen: BaseScreen? = null
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
if (menu != null) baseMenu?.onCreate(menuInflater, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (item == null) return false
return baseMenu?.onItemSelected(item) ?: false
}
fun restartWithFade(cls: Class<*>?) {
Handler().postDelayed({
finish()
overridePendingTransition(anim.fade_in, anim.fade_out)
startActivity(Intent(this, cls))
}, 500) // HACK: Let the menu disappear first
}
fun setBaseMenu(baseMenu: BaseMenu?) {
this.baseMenu = baseMenu
}
fun setScreen(screen: BaseScreen?) {
this.screen = screen
}
fun showDialog(dialog: AppCompatDialogFragment, tag: String?) {
dialog.show(supportFragmentManager, tag)
}
fun showDialog(dialog: AppCompatDialog) {
dialog.show()
}
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
val screen = screen
if(screen == null) super.onActivityResult(request, result, data)
else screen.onResult(request, result, data)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.setDefaultUncaughtExceptionHandler(getExceptionHandler())
}
private fun getExceptionHandler() = BaseExceptionHandler(this)
override fun onResume() {
super.onResume()
screen?.reattachDialogs()
}
override fun startActivity(intent: Intent?) {
try {
super.startActivity(intent)
} catch(e: ActivityNotFoundException) {
this.screen?.showMessage(R.string.activity_not_found)
}
}
}

View File

@@ -17,31 +17,22 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities;
import android.content.*;
package org.isoron.androidbase.activities;
import dagger.*;
@Module
public class ActivityModule
public class BaseActivityModule
{
private BaseActivity activity;
public ActivityModule(BaseActivity activity)
public BaseActivityModule(BaseActivity activity)
{
this.activity = activity;
}
@Provides
public BaseActivity getActivity()
{
return activity;
}
@Provides
@ActivityContext
public Context getContext()
public BaseActivity getBaseActivity()
{
return activity;
}

View File

@@ -16,66 +16,50 @@
* 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.androidbase.activities
package org.isoron.uhabits.activities;
import android.support.annotation.*;
import android.view.*;
import javax.annotation.*;
import android.view.*
import androidx.annotation.*
/**
* Base class for all the menus in the application.
* <p>
*
* This class receives from BaseActivity all callbacks related to menus, such as
* menu creation and click events. It also handles some implementation details
* of creating menus in Android, such as inflating the resources.
*/
public abstract class BaseMenu
{
@NonNull
private final BaseActivity activity;
public BaseMenu(@NonNull BaseActivity activity)
{
this.activity = activity;
}
abstract class BaseMenu(private val activity: BaseActivity) {
/**
* Declare that the menu has changed, and should be recreated.
*/
public void invalidate()
{
activity.invalidateOptionsMenu();
fun invalidate() {
activity.invalidateOptionsMenu()
}
/**
* Called when the menu is first displayed.
* <p>
*
* The given menu is already inflated and ready to receive items. The
* application should override this method and add items to the menu here.
*
* @param menu the menu that is being created.
*/
public void onCreate(@NonNull Menu menu)
{
}
open fun onCreate(menu: Menu) {}
/**
* Called when the menu is first displayed.
* <p>
* This method cannot be overridden. The application should override the
* methods onCreate(Menu) and getMenuResourceId instead.
*
* This method should not be overridden. The application should override
* the methods onCreate(Menu) and getMenuResourceId instead.
*
* @param inflater a menu inflater, for creating the menu
* @param menu the menu that is being created.
*/
public final void onCreate(@NonNull MenuInflater inflater,
@NonNull Menu menu)
{
menu.clear();
inflater.inflate(getMenuResourceId(), menu);
onCreate(menu);
fun onCreate(inflater: MenuInflater, menu: Menu) {
menu.clear()
inflater.inflate(getMenuResourceId(), menu)
onCreate(menu)
}
/**
@@ -84,16 +68,14 @@ public abstract class BaseMenu
* @param item the item that was selected.
* @return true if the event was consumed, or false otherwise
*/
public boolean onItemSelected(@NonNull MenuItem item)
{
return false;
}
open fun onItemSelected(item: MenuItem): Boolean = false
/**
* Returns the id of the resource that should be used to inflate this menu.
*
* @return id of the menu resource.
*/
@Resource
protected abstract int getMenuResourceId();
}
@MenuRes
protected abstract fun getMenuResourceId(): Int
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.androidbase.activities
import android.content.Context
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.widget.Toolbar
import org.isoron.androidbase.R
import org.isoron.androidbase.utils.InterfaceUtils.dpToPixels
import org.isoron.androidbase.utils.StyledResources
/**
* Base class for all root views in the application.
*
*
* A root view is an Android view that is directly attached to an activity. This
* view usually includes a toolbar and a progress bar. This abstract class hides
* some of the complexity of setting these things up, for every version of
* Android.
*/
abstract class BaseRootView(context: Context) : FrameLayout(context) {
var displayHomeAsUp = false
var screen: BaseScreen? = null
private set
open fun getToolbar(): Toolbar {
return findViewById(R.id.toolbar)
?: throw RuntimeException("Your BaseRootView should have a toolbar with id R.id.toolbar")
}
open fun getToolbarColor(): Int = StyledResources(context).getColor(R.attr.colorPrimary)
protected open fun initToolbar() {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
getToolbar().elevation = dpToPixels(context, 2f)
findViewById<View>(R.id.toolbarShadow)?.visibility = View.GONE
findViewById<View>(R.id.headerShadow)?.visibility = View.GONE
}
}
fun onAttachedToScreen(screen: BaseScreen?) {
this.screen = screen
}
}

View File

@@ -0,0 +1,237 @@
/*
* 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.androidbase.activities
import android.content.*
import android.graphics.*
import android.graphics.drawable.*
import android.view.*
import android.widget.*
import androidx.annotation.*
import androidx.appcompat.app.*
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.content.*
import com.google.android.material.snackbar.*
import org.isoron.androidbase.*
import org.isoron.androidbase.utils.*
import org.isoron.androidbase.utils.ColorUtils.mixColors
import org.isoron.androidbase.utils.InterfaceUtils.dpToPixels
import java.io.*
/**
* Base class for all screens in the application.
*
* Screens are responsible for deciding what root views and what menus should be attached to the
* main window. They are also responsible for showing other screens and for receiving their results.
*/
open class BaseScreen(@JvmField protected var activity: BaseActivity) {
private var rootView: BaseRootView? = null
private var selectionMenu: BaseSelectionMenu? = null
private var snackbar: Snackbar? = null
/**
* Notifies the screen that its contents should be updated.
*/
fun invalidate() {
rootView?.invalidate()
}
fun invalidateToolbar() {
rootView?.let { root ->
activity.runOnUiThread {
val toolbar = root.getToolbar()
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.let { actionBar ->
actionBar.setDisplayHomeAsUpEnabled(root.displayHomeAsUp)
val color = root.getToolbarColor()
setActionBarColor(actionBar, color)
setStatusBarColor(color)
}
}
}
}
/**
* Called when another Activity has finished, and has returned some result.
*
* @param requestCode the request code originally supplied to startActivityForResult.
* @param resultCode the result code sent by the other activity.
* @param data an Intent containing extra data sent by the other
* activity.
* @see {@link android.app.Activity.onActivityResult
*/
open fun onResult(requestCode: Int, resultCode: Int, data: Intent?) {}
/**
* Called after activity has been recreated, and the dialogs should be
* reattached to their controllers.
*/
open fun reattachDialogs() {}
/**
* Sets the menu to be shown by this screen.
*
*
* This menu will be visible if when there is no active selection operation.
* If the provided menu is null, then no menu will be shown.
*
* @param menu the menu to be shown.
*/
fun setMenu(menu: BaseMenu?) {
activity.setBaseMenu(menu)
}
/**
* Sets the root view for this screen.
*
* @param rootView the root view for this screen.
*/
fun setRootView(rootView: BaseRootView?) {
this.rootView = rootView
activity.setContentView(rootView)
rootView?.let {
it.onAttachedToScreen(this)
invalidateToolbar()
}
}
/**
* Sets the menu to be shown when a selection is active on the screen.
*
* @param menu the menu to be shown during a selection
*/
fun setSelectionMenu(menu: BaseSelectionMenu?) {
selectionMenu = menu
}
/**
* Shows a message on the screen.
*
* @param stringId the string resource id for this message.
*/
fun showMessage(@StringRes stringId: Int?, rootView: View?) {
var snackbar = this.snackbar
if (stringId == null || rootView == null) return
if (snackbar == null) {
snackbar = Snackbar.make(rootView, stringId, Snackbar.LENGTH_SHORT)
val tvId = R.id.snackbar_text
val tv = snackbar.view.findViewById<TextView>(tvId)
tv.setTextColor(Color.WHITE)
this.snackbar = snackbar
}
snackbar.setText(stringId)
snackbar.show()
}
fun showMessage(@StringRes stringId: Int?) {
showMessage(stringId, this.rootView)
}
fun showSendEmailScreen(@StringRes toId: Int, @StringRes subjectId: Int, content: String?) {
val to = activity.getString(toId)
val subject = activity.getString(subjectId)
activity.startActivity(Intent().apply {
action = Intent.ACTION_SEND
type = "message/rfc822"
putExtra(Intent.EXTRA_EMAIL, arrayOf(to))
putExtra(Intent.EXTRA_SUBJECT, subject)
putExtra(Intent.EXTRA_TEXT, content)
})
}
fun showSendFileScreen(archiveFilename: String) {
val file = File(archiveFilename)
val fileUri = FileProvider.getUriForFile(activity, "org.isoron.uhabits", file)
activity.startActivity(Intent().apply {
action = Intent.ACTION_SEND
type = "application/zip"
putExtra(Intent.EXTRA_STREAM, fileUri)
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
})
}
/**
* Instructs the screen to start a selection.
*
* If a selection menu was provided, this menu will be shown instead of the regular one.
*/
fun startSelection() {
activity.startSupportActionMode(ActionModeWrapper())
}
private fun setActionBarColor(actionBar: ActionBar, color: Int) {
val drawable = ColorDrawable(color)
actionBar.setBackgroundDrawable(drawable)
}
private fun setStatusBarColor(baseColor: Int) {
val darkerColor = mixColors(baseColor, Color.BLACK, 0.75f)
activity.window.statusBarColor = darkerColor
}
private inner class ActionModeWrapper : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
val selectionMenu = selectionMenu
if (item == null || selectionMenu == null) return false
return selectionMenu.onItemClicked(item)
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
if (mode == null || menu == null) return false
val selectionMenu = selectionMenu ?: return false
selectionMenu.onCreate(activity.menuInflater, mode, menu)
return true
}
override fun onDestroyActionMode(mode: ActionMode?) {
selectionMenu?.onFinish()
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val selectionMenu = selectionMenu
if (selectionMenu == null || menu == null) return false
return selectionMenu.onPrepare(menu)
}
}
companion object {
@JvmStatic
@Deprecated("")
fun getDefaultActionBarColor(context: Context) =
StyledResources(context).getColor(R.attr.colorPrimary)
@JvmStatic
@Deprecated("")
fun setupActionBarColor(activity: AppCompatActivity, color: Int) {
val toolbar = activity.findViewById<Toolbar>(R.id.toolbar) ?: return
activity.setSupportActionBar(toolbar)
val supportActionBar = activity.supportActionBar ?: return
supportActionBar.setDisplayHomeAsUpEnabled(true)
val drawable = ColorDrawable(color)
supportActionBar.setBackgroundDrawable(drawable)
val darkerColor = mixColors(color, Color.BLACK, 0.75f)
activity.window.statusBarColor = darkerColor
toolbar.elevation = dpToPixels(activity, 2f)
activity.findViewById<View>(R.id.toolbarShadow)?.visibility = View.GONE
activity.findViewById<View>(R.id.headerShadow)?.visibility = View.GONE
}
}
}

View File

@@ -16,72 +16,60 @@
* 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.androidbase.activities
package org.isoron.uhabits.activities;
import android.support.annotation.*;
import android.support.v7.view.ActionMode;
import android.view.*;
import android.view.*
import androidx.appcompat.view.ActionMode
/**
* Base class for all the selection menus in the application.
* <p>
*
* A selection menu is a menu that appears when the screen starts a selection
* operation. It contains actions that modify the selected items, such as delete
* or archive. Since it replaces the toolbar, it also has a title.
* <p>
*
* This class hides many implementation details of creating such menus in
* Android. The interface is supposed to look very similar to {@link BaseMenu},
* Android. The interface is supposed to look very similar to [BaseMenu],
* with a few additional methods, such as finishing the selection operation.
* Internally, it uses an {@link ActionMode}.
* Internally, it uses an [ActionMode].
*/
public abstract class BaseSelectionMenu
{
@Nullable
private ActionMode actionMode;
abstract class BaseSelectionMenu {
private var actionMode: ActionMode? = null
/**
* Finishes the selection operation.
*/
public void finish()
{
if (actionMode != null) actionMode.finish();
fun finish() {
actionMode?.finish()
}
/**
* Declare that the menu has changed, and should be recreated.
*/
public void invalidate()
{
if (actionMode != null) actionMode.invalidate();
fun invalidate() {
actionMode?.invalidate()
}
/**
* Called when the menu is first displayed.
* <p>
* This method cannot be overridden. The application should override the
* methods onCreate(Menu) and getMenuResourceId instead.
*
* This method should not be overridden. The application should override
* the methods onCreate(Menu) and getMenuResourceId instead.
*
* @param inflater a menu inflater, for creating the menu
* @param mode the action mode associated with this menu.
* @param menu the menu that is being created.
*/
public final void onCreate(@NonNull MenuInflater inflater,
@NonNull ActionMode mode,
@NonNull Menu menu)
{
this.actionMode = mode;
inflater.inflate(getResourceId(), menu);
onCreate(menu);
fun onCreate(inflater: MenuInflater, mode: ActionMode, menu: Menu) {
actionMode = mode
inflater.inflate(getResourceId(), menu)
onCreate(menu)
}
/**
* Called when the selection operation is about to finish.
*/
public void onFinish()
{
}
open fun onFinish() {}
/**
* Called whenever an item on the menu is selected.
@@ -89,11 +77,7 @@ public abstract class BaseSelectionMenu
* @param item the item that was selected.
* @return true if the event was consumed, or false otherwise
*/
public boolean onItemClicked(@NonNull MenuItem item)
{
return false;
}
open fun onItemClicked(item: MenuItem): Boolean = false
/**
* Called whenever the menu is invalidated.
@@ -101,29 +85,23 @@ public abstract class BaseSelectionMenu
* @param menu the menu to be refreshed
* @return true if the menu has changes, false otherwise
*/
public boolean onPrepare(@NonNull Menu menu)
{
return false;
}
open fun onPrepare(menu: Menu): Boolean = false
/**
* Sets the title of the selection menu.
*
* @param title the new title.
*/
public void setTitle(String title)
{
if (actionMode != null) actionMode.setTitle(title);
fun setTitle(title: String?) {
actionMode?.title = title
}
protected abstract int getResourceId();
protected abstract fun getResourceId(): Int
/**
* Called when the menu is first created.
*
* @param menu the menu being created
*/
protected void onCreate(@NonNull Menu menu)
{
}
}
protected fun onCreate(menu: Menu) {}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.androidbase.utils
import android.graphics.Color
import kotlin.math.max
object ColorUtils {
private const val ALPHA_CHANNEL = 24
private const val RED_CHANNEL = 16
private const val GREEN_CHANNEL = 8
private const val BLUE_CHANNEL = 0
@JvmStatic
fun mixColors(color1: Int, color2: Int, amount: Float): Int {
val a = mixColorChannel(color1, color2, amount, ALPHA_CHANNEL)
val r = mixColorChannel(color1, color2, amount, RED_CHANNEL)
val g = mixColorChannel(color1, color2, amount, GREEN_CHANNEL)
val b = mixColorChannel(color1, color2, amount, BLUE_CHANNEL)
return a or r or g or b
}
@JvmStatic
fun setAlpha(color: Int, newAlpha: Float): Int {
val intAlpha = (newAlpha * 255).toInt()
return Color.argb(intAlpha, Color.red(color), Color.green(color), Color.blue(color))
}
@JvmStatic
fun setMinValue(color: Int, newValue: Float): Int {
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[2] = max(hsv[2], newValue)
return Color.HSVToColor(hsv)
}
private fun mixColorChannel(color1: Int, color2: Int, amount: Float, channel: Int): Int {
val fl = (color1 shr channel and 0xff).toFloat() * amount
val f2 = (color2 shr channel and 0xff).toFloat() * (1.0f - amount)
return (fl + f2).toInt() and 0xff shl channel
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 Á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.androidbase.utils
import android.os.Environment
import android.util.Log
import java.io.*
fun File.copyTo(dst: File) {
val inStream = FileInputStream(this)
val outStream = FileOutputStream(dst)
inStream.copyTo(outStream)
}
fun InputStream.copyTo(dst: File) {
val outStream = FileOutputStream(dst)
this.copyTo(outStream)
}
fun InputStream.copyTo(out: OutputStream) {
var numBytes: Int
val buffer = ByteArray(1024)
while (this.read(buffer).also { numBytes = it } != -1) {
out.write(buffer, 0, numBytes)
}
}
object FileUtils {
@JvmStatic
fun getDir(potentialParentDirs: Array<File>, relativePath: String): File? {
val chosenDir: File? = potentialParentDirs.firstOrNull { dir -> dir.canWrite() }
if (chosenDir == null) {
Log.e("FileUtils", "getDir: all potential parents are null or non-writable")
return null
}
val dir = File("${chosenDir.absolutePath}/${relativePath}/")
if (!dir.exists() && !dir.mkdirs()) {
Log.e("FileUtils", "getDir: chosen dir does not exist and cannot be created")
return null
}
return dir
}
@JvmStatic
fun getSDCardDir(relativePath: String): File? {
val parents = arrayOf(Environment.getExternalStorageDirectory())
return getDir(parents, relativePath)
}
}

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.androidbase.utils
import android.content.*
import android.graphics.*
import android.util.*
import android.view.*
import android.widget.*
import android.widget.TextView.*
import androidx.core.view.*
object InterfaceUtils {
private var fontAwesome: Typeface? = null
private var fixedResolution: Float? = null
@JvmStatic
fun setFixedResolution(f: Float) {
fixedResolution = f
}
@JvmStatic
fun getFontAwesome(context: Context): Typeface? {
if (fontAwesome == null) {
fontAwesome = Typeface.createFromAsset(context.assets, "fontawesome-webfont.ttf")
}
return fontAwesome
}
@JvmStatic
fun dpToPixels(context: Context, dp: Float): Float {
if (fixedResolution != null) return dp * fixedResolution!!
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dp,
context.resources.displayMetrics)
}
@JvmStatic
fun spToPixels(context: Context, sp: Float): Float {
if (fixedResolution != null) return sp * fixedResolution!!
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
sp,
context.resources.displayMetrics)
}
@JvmStatic
fun getDimension(context: Context, id: Int): Float {
val dim = context.resources.getDimension(id)
if (fixedResolution != null) {
val actualDensity = context.resources.displayMetrics.density
return dim / actualDensity * fixedResolution!!
}
return dim
}
fun setupEditorAction(parent: ViewGroup,
listener: OnEditorActionListener) {
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
if (child is ViewGroup) setupEditorAction(child, listener)
if (child is TextView) child.setOnEditorActionListener(listener)
}
}
fun isLayoutRtl(view: View?): Boolean {
return ViewCompat.getLayoutDirection(view!!) ==
ViewCompat.LAYOUT_DIRECTION_RTL
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.androidbase.utils
import android.content.Context
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
import androidx.annotation.AttrRes
import org.isoron.androidbase.R
class StyledResources(private val context: Context) {
fun getBoolean(@AttrRes attrId: Int): Boolean {
val ta = getTypedArray(attrId)
val bool = ta.getBoolean(0, false)
ta.recycle()
return bool
}
fun getDimension(@AttrRes attrId: Int): Int {
val ta = getTypedArray(attrId)
val dim = ta.getDimensionPixelSize(0, 0)
ta.recycle()
return dim
}
fun getColor(@AttrRes attrId: Int): Int {
val ta = getTypedArray(attrId)
val color = ta.getColor(0, 0)
ta.recycle()
return color
}
fun getDrawable(@AttrRes attrId: Int): Drawable? {
val ta = getTypedArray(attrId)
val drawable = ta.getDrawable(0)
ta.recycle()
return drawable
}
fun getFloat(@AttrRes attrId: Int): Float {
val ta = getTypedArray(attrId)
val f = ta.getFloat(0, 0f)
ta.recycle()
return f
}
fun getPalette(): IntArray {
val resourceId = getResource(R.attr.palette)
if (resourceId < 0) throw RuntimeException("palette resource not found")
return context.resources.getIntArray(resourceId)
}
fun getResource(@AttrRes attrId: Int): Int {
val ta = getTypedArray(attrId)
val resourceId = ta.getResourceId(0, -1)
ta.recycle()
return resourceId
}
private fun getTypedArray(@AttrRes attrId: Int): TypedArray {
val attrs = intArrayOf(attrId)
if (fixedTheme != null) {
return context.theme.obtainStyledAttributes(fixedTheme!!, attrs)
}
return context.obtainStyledAttributes(attrs)
}
companion object {
private var fixedTheme: Int? = null
@JvmStatic
fun setFixedTheme(theme: Int?) {
fixedTheme = theme
}
}
}

View File

@@ -0,0 +1,6 @@
<resources>
<item name="toolbar" type="id" />
<item name="toolbarShadow" type="id" />
<item name="headerShadow" type="id" />
<attr name="palette" format="reference"/>
</resources>

View File

@@ -1,97 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<array name="lightPalette">
<item>@color/red_700</item>
<item>@color/deep_orange_700</item>
<item>@color/yellow_800</item>
<item>@color/lime_700</item>
<item>@color/green_700</item>
<item>@color/teal_600</item>
<item>@color/cyan_600</item>
<item>@color/light_blue_600</item>
<item>@color/deep_purple_600</item>
<item>@color/purple_600</item>
<item>@color/pink_600</item>
<item>@color/grey_800</item>
<item>@color/grey_500</item>
</array>
<array name="darkPalette">
<item>@color/red_200</item>
<item>@color/deep_orange_200</item>
<item>@color/yellow_200</item>
<item>@color/lime_200</item>
<item>@color/green_A200</item>
<item>@color/teal_200</item>
<item>@color/cyan_200</item>
<item>@color/light_blue_200</item>
<item>@color/deep_purple_200</item>
<item>@color/purple_200</item>
<item>@color/pink_200</item>
<item>@color/grey_100</item>
<item>@color/grey_500</item>
</array>
<array name="transparentWidgetPalette">
<item>@color/red_800</item>
<item>@color/deep_orange_800</item>
<item>@color/yellow_800</item>
<item>@color/lime_800</item>
<item>@color/green_700</item>
<item>@color/teal_700</item>
<item>@color/cyan_700</item>
<item>@color/light_blue_700</item>
<item>@color/deep_purple_700</item>
<item>@color/purple_700</item>
<item>@color/pink_700</item>
<item>@color/black_aa</item>
<item>@color/black_aa</item>
</array>
<!-- Time and Date picker -->
<color name="circle_background">#f2f2f2</color>
<color name="line_background">#cccccc</color>
<color name="ampm_text_color">#8c8c8c</color>
<color name="done_text_color_normal">#000000</color>
<color name="done_text_color_disabled">#cccccc</color>
<color name="numbers_text_color">#8c8c8c</color>
<color name="transparent">#00000000</color>
<color name="transparent_black">#7f000000</color>
<color name="blue">#33b5e5</color>
<color name="blue_focused">#c1e8f7</color>
<color name="neutral_pressed">#33999999</color>
<color name="darker_blue">#0099cc</color>
<color name="date_picker_text_normal">#ff999999</color>
<color name="calendar_header">#999999</color>
<color name="date_picker_view_animator">#f2f2f2</color>
<color name="calendar_selected_date_text">#ffd1d2d4</color>
<color name="done_text_color">#888888</color>
<color name="done_text_color_dark">#888888</color>
<!-- Colors for red theme -->
<color name="red">#ff3333</color>
<color name="red_focused">#853333</color>
<color name="light_gray">#404040</color>
<color name="dark_gray">#363636</color>
<color name="line_dark">#808080</color>
<!-- Material design color palette -->
<color name="red_50">#FFEBEE</color>
<color name="red_100">#FFCDD2</color>
<color name="red_200">#EF9A9A</color>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~ Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
@@ -18,7 +17,7 @@
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<!-- App introduction -->
<!-- Middle part of the sentence '1 time in xx days' -->
</resources>
<string name="activity_not_found">No app was found to support this action</string>
</resources>

1
android/android-pickers/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,24 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion COMPILE_SDK_VERSION as Integer
defaultConfig {
minSdkVersion MIN_SDK_VERSION as Integer
targetSdkVersion TARGET_SDK_VERSION as Integer
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
}

View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest package="com.android"
xmlns:android="http://schemas.android.com/apk/res/android"/>

View File

@@ -16,18 +16,15 @@
package com.android.colorpicker;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.app.*;
import android.os.*;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.*;
import android.view.*;
import android.widget.*;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
import org.isoron.uhabits.R;
import com.android.*;
import com.android.colorpicker.ColorPickerSwatch.*;
/**
* A dialog which takes in as input an array of palette and creates a palette allowing the user to

View File

@@ -16,18 +16,14 @@
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TableLayout;
import android.widget.TableRow;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
import com.android.*;
import com.android.colorpicker.ColorPickerSwatch.*;
/**
* A color picker custom view which creates an grid of color squares. The number of squares per

View File

@@ -16,14 +16,12 @@
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.content.*;
import android.graphics.drawable.*;
import android.view.*;
import android.widget.*;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.*;
/**
* Creates a circular swatch of a specified color. Adds a checkmark if marked as checked.

View File

@@ -16,36 +16,23 @@
package com.android.datetimepicker.date;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import android.animation.*;
import android.app.*;
import android.content.res.*;
import android.os.*;
import android.text.format.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.view.animation.*;
import android.widget.*;
import org.isoron.uhabits.R;
import com.android.*;
import com.android.datetimepicker.*;
import com.android.datetimepicker.date.MonthAdapter.*;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.date.MonthAdapter.CalendarDay;
import java.text.*;
import java.util.*;
/**
* Dialog allowing users to select a date.

View File

@@ -16,37 +16,27 @@
package com.android.datetimepicker.date;
import java.security.InvalidParameterException;
import java.util.Calendar;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.os.*;
import androidx.core.view.*;
import androidx.core.view.accessibility.*;
import androidx.core.widget.*;
import android.text.format.*;
import android.view.*;
import android.view.accessibility.*;
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.*;
import com.android.datetimepicker.*;
import com.android.datetimepicker.date.MonthAdapter.*;
import java.security.*;
import java.util.*;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import org.isoron.uhabits.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.date.MonthAdapter.CalendarDay;
/**
* A calendar-like view displaying a specified month and the appropriate selectable day numbers

View File

@@ -16,16 +16,14 @@
package com.android.datetimepicker.date;
import org.isoron.uhabits.R;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.util.*;
import android.widget.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.widget.TextView;
import com.android.*;
/**
* A text view which, when pressed or activated, displays a blue circle around the text.

View File

@@ -16,24 +16,18 @@
package com.android.datetimepicker.date;
import java.util.ArrayList;
import java.util.List;
import android.content.*;
import android.content.res.*;
import android.graphics.drawable.*;
import android.view.*;
import android.view.accessibility.*;
import android.widget.*;
import android.widget.AdapterView.*;
import org.isoron.uhabits.R;
import com.android.*;
import com.android.datetimepicker.date.DatePickerDialog.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.StateListDrawable;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.android.datetimepicker.date.DatePickerDialog.OnDateChangedListener;
import java.util.*;
/**
* Displays a selectable list of years.

View File

@@ -16,20 +16,17 @@
package com.android.datetimepicker.time;
import java.text.DateFormatSymbols;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.util.*;
import android.view.*;
import org.isoron.uhabits.R;
import com.android.*;
import com.android.datetimepicker.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.util.Log;
import android.view.View;
import com.android.datetimepicker.Utils;
import java.text.*;
/**
* Draw the two smaller AM and PM circles next to where the larger circle will be.
@@ -44,8 +41,8 @@ public class AmPmCirclesView extends View {
private final Paint mPaint = new Paint();
private int mSelectedAlpha;
private int mUnselectedColor;
private int mAmPmTextColor;
private int mSelectedColor;
protected int mAmPmTextColor = Color.WHITE;
protected int mSelectedColor = Color.BLUE;
private float mCircleRadiusMultiplier;
private float mAmPmCircleRadiusMultiplier;
private String mAmText;
@@ -76,8 +73,8 @@ public class AmPmCirclesView extends View {
Resources res = context.getResources();
mUnselectedColor = res.getColor(R.color.white);
mSelectedColor = res.getColor(R.color.blue);
mAmPmTextColor = res.getColor(R.color.ampm_text_color);
//mSelectedColor = res.getColor(R.color.blue);
//mAmPmTextColor = res.getColor(R.color.ampm_text_color);
mSelectedAlpha = SELECTED_ALPHA;
String typefaceFamily = res.getString(R.string.sans_serif);
Typeface tf = Typeface.create(typefaceFamily, Typeface.NORMAL);
@@ -108,8 +105,8 @@ public class AmPmCirclesView extends View {
mSelectedAlpha = SELECTED_ALPHA_THEME_DARK;
} else {
mUnselectedColor = res.getColor(R.color.white);
mSelectedColor = res.getColor(R.color.blue);
mAmPmTextColor = res.getColor(R.color.ampm_text_color);
//mSelectedColor = res.getColor(R.color.blue);
//mAmPmTextColor = res.getColor(R.color.ampm_text_color);
mSelectedAlpha = SELECTED_ALPHA;
}
}

View File

@@ -16,14 +16,14 @@
package com.android.datetimepicker.time;
import org.isoron.uhabits.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import com.android.*;
/**
* Draws a simple white circle on which the numbers will be drawn.

View File

@@ -16,30 +16,20 @@
package com.android.datetimepicker.time;
import org.isoron.uhabits.R;
import android.animation.*;
import android.annotation.*;
import android.content.*;
import android.content.res.*;
import android.os.*;
import android.text.format.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.view.accessibility.*;
import android.widget.*;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.*;
import com.android.datetimepicker.*;
/**
* The primary layout to hold the circular picker, and the am/pm buttons. This view well measure
@@ -94,6 +84,14 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private AnimatorSet mTransition;
private Handler mHandler = new Handler();
public void setColor(int selectedColor)
{
mHourRadialSelectorView.mPaint.setColor(selectedColor);
mMinuteRadialSelectorView.mPaint.setColor(selectedColor);
mAmPmCirclesView.mSelectedColor = selectedColor;
mAmPmCirclesView.mAmPmTextColor = selectedColor;
}
public interface OnValueSelectedListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}

View File

@@ -16,21 +16,16 @@
package com.android.datetimepicker.time;
import org.isoron.uhabits.R;
import android.animation.*;
import android.animation.ValueAnimator.*;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import com.android.datetimepicker.Utils;
import com.android.*;
import com.android.datetimepicker.*;
/**
* View to show what number is selected. This will draw a blue circle over the number, with a blue
@@ -45,7 +40,7 @@ public class RadialSelectorView extends View {
// Alpha level for the line.
private static final int FULL_ALPHA = Utils.FULL_ALPHA;
private final Paint mPaint = new Paint();
protected final Paint mPaint = new Paint();
private boolean mIsInitialized;
private boolean mDrawValuesReady;
@@ -101,8 +96,6 @@ public class RadialSelectorView extends View {
Resources res = context.getResources();
int blue = res.getColor(R.color.blue);
mPaint.setColor(blue);
mPaint.setAntiAlias(true);
mSelectionAlpha = SELECTED_ALPHA;
@@ -144,15 +137,11 @@ public class RadialSelectorView extends View {
/* package */ void setTheme(Context context, boolean themeDark) {
Resources res = context.getResources();
int color;
if (themeDark) {
color = res.getColor(R.color.red);
mSelectionAlpha = SELECTED_ALPHA_THEME_DARK;
} else {
color = res.getColor(R.color.blue);
mSelectionAlpha = SELECTED_ALPHA;
}
mPaint.setColor(color);
}
/**

View File

@@ -16,21 +16,16 @@
package com.android.datetimepicker.time;
import org.isoron.uhabits.R;
import android.animation.*;
import android.animation.ValueAnimator.*;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.util.*;
import android.view.*;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.util.Log;
import android.view.View;
import com.android.*;
/**
* A view to show a series of numbers in a circular pattern.

View File

@@ -16,42 +16,33 @@
package com.android.datetimepicker.time;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Locale;
import org.isoron.uhabits.R;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.animation.*;
import android.annotation.*;
import android.app.ActionBar.*;
import android.app.*;
import android.app.ActionBar;
import android.app.ActionBar.LayoutParams;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v7.app.*;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.content.*;
import android.content.res.*;
import android.os.*;
import com.android.datetimepicker.HapticFeedbackController;
import com.android.datetimepicker.Utils;
import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener;
import androidx.appcompat.app.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import com.android.*;
import com.android.datetimepicker.*;
import com.android.datetimepicker.time.RadialPickerLayout.*;
import java.text.*;
import java.util.*;
/**
* Dialog to set a time.
*/
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener{
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener
{
private static final String TAG = "TimePickerDialog";
private static final String KEY_HOUR_OF_DAY = "hour_of_day";
@@ -61,6 +52,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
private static final String KEY_IN_KB_MODE = "in_kb_mode";
private static final String KEY_TYPED_TIMES = "typed_times";
private static final String KEY_DARK_THEME = "dark_theme";
private static final String KEY_SELECTED_COLOR = "selected_color";
public static final int HOUR_INDEX = 0;
public static final int MINUTE_INDEX = 1;
@@ -75,6 +67,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
private static final int PULSE_ANIMATOR_DELAY = 300;
private OnTimeSetListener mCallback;
private DialogInterface.OnDismissListener dismissListener;
private HapticFeedbackController mHapticFeedbackController;
@@ -119,37 +112,50 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* The callback interface used to indicate the user is done filling in
* the time (they clicked on the 'Set' button).
*/
public interface OnTimeSetListener {
public interface OnTimeSetListener
{
/**
* @param view The view associated with this listener.
* @param view The view associated with this listener.
* @param hourOfDay The hour that was set.
* @param minute The minute that was set.
* @param minute The minute that was set.
*/
void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute);
void onTimeCleared(RadialPickerLayout view);
default void onTimeCleared(RadialPickerLayout view)
{
}
}
public TimePickerDialog() {
public TimePickerDialog()
{
// Empty constructor required for dialog fragment.
}
@SuppressLint("Java")
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.
}
public static TimePickerDialog newInstance(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
TimePickerDialog ret = new TimePickerDialog();
ret.initialize(callback, hourOfDay, minute, is24HourMode);
ret.initialize(callback, hourOfDay, minute, is24HourMode, color);
return ret;
}
public void initialize(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
int hourOfDay,
int minute,
boolean is24HourMode,
int color)
{
mCallback = callback;
mInitialHourOfDay = hourOfDay;
@@ -157,40 +163,47 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mIs24HourMode = is24HourMode;
mInKbMode = false;
mThemeDark = false;
mSelectedColor = color;
}
/**
* Set a dark or light theme. NOTE: this will only take effect for the next onCreateView.
*/
public void setThemeDark(boolean dark) {
public void setThemeDark(boolean dark)
{
mThemeDark = dark;
}
public boolean isThemeDark() {
public boolean isThemeDark()
{
return mThemeDark;
}
public void setOnTimeSetListener(OnTimeSetListener callback) {
public void setOnTimeSetListener(OnTimeSetListener callback)
{
mCallback = callback;
}
public void setStartTime(int hourOfDay, int minute) {
public void setStartTime(int hourOfDay, int minute)
{
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
mInKbMode = false;
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_HOUR_OF_DAY)
&& savedInstanceState.containsKey(KEY_MINUTE)
&& savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
&& savedInstanceState.containsKey(KEY_MINUTE)
&& savedInstanceState.containsKey(KEY_IS_24_HOUR_VIEW)) {
mInitialHourOfDay = savedInstanceState.getInt(KEY_HOUR_OF_DAY);
mInitialMinute = savedInstanceState.getInt(KEY_MINUTE);
mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
mInKbMode = savedInstanceState.getBoolean(KEY_IN_KB_MODE);
mThemeDark = savedInstanceState.getBoolean(KEY_DARK_THEME);
mSelectedColor = savedInstanceState.getInt(KEY_SELECTED_COLOR);
}
}
@@ -202,7 +215,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle savedInstanceState)
{
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
View view = inflater.inflate(R.layout.time_picker_dialog, null);
@@ -214,8 +228,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mSelectHours = res.getString(R.string.select_hours);
mMinutePickerDescription = res.getString(R.string.minute_picker_description);
mSelectMinutes = res.getString(R.string.select_minutes);
mSelectedColor = res.getColor(mThemeDark? R.color.red : R.color.blue);
mUnselectedColor = res.getColor(mThemeDark? R.color.white : R.color.numbers_text_color);
//mSelectedColor = res.getColor(mThemeDark ? R.color.red : R.color.blue);
mUnselectedColor = res.getColor(mThemeDark ? R.color.white : R.color.numbers_text_color);
mHourView = (TextView) view.findViewById(R.id.hours);
mHourView.setOnKeyListener(keyboardListener);
@@ -234,8 +248,9 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mTimePicker = (RadialPickerLayout) view.findViewById(R.id.time_picker);
mTimePicker.setOnValueSelectedListener(this);
mTimePicker.setOnKeyListener(keyboardListener);
mTimePicker.setColor(mSelectedColor);
mTimePicker.initialize(getActivity(), mHapticFeedbackController, mInitialHourOfDay,
mInitialMinute, mIs24HourMode);
mInitialMinute, mIs24HourMode);
int currentItemShowing = HOUR_INDEX;
if (savedInstanceState != null &&
@@ -245,25 +260,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
setCurrentItemShowing(currentItemShowing, false, true, true);
mTimePicker.invalidate();
mHourView.setOnClickListener(new OnClickListener() {
mHourView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
setCurrentItemShowing(HOUR_INDEX, true, false, true);
tryVibrate();
}
});
mMinuteView.setOnClickListener(new OnClickListener() {
mMinuteView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
setCurrentItemShowing(MINUTE_INDEX, true, false, true);
tryVibrate();
}
});
mDoneButton = (TextView) view.findViewById(R.id.done_button);
mDoneButton.setOnClickListener(new OnClickListener() {
mDoneButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
if (mInKbMode && isTypedTimeFullyLegal()) {
finishKbMode(false);
} else {
@@ -271,25 +292,25 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
if (mCallback != null) {
mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes());
mTimePicker.getHours(), mTimePicker.getMinutes());
}
dismiss();
}
});
mDoneButton.setOnKeyListener(keyboardListener);
mClearButton = (TextView) view.findViewById(R.id.clear_button);
mClearButton.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if(mCallback != null) {
mCallback.onTimeCleared(mTimePicker);
}
dismiss();
}
});
{
@Override
public void onClick(View v)
{
if (mCallback != null) {
mCallback.onTimeCleared(mTimePicker);
}
dismiss();
}
});
mClearButton.setOnKeyListener(keyboardListener);
// Enable or disable the AM/PM view.
@@ -304,15 +325,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
separatorView.setLayoutParams(paramsSeparator);
} else {
mAmPmTextView.setVisibility(View.VISIBLE);
updateAmPmDisplay(mInitialHourOfDay < 12? AM : PM);
mAmPmHitspace.setOnClickListener(new OnClickListener() {
updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM);
mAmPmHitspace.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
public void onClick(View v)
{
tryVibrate();
int amOrPm = mTimePicker.getIsCurrentlyAmOrPm();
if (amOrPm == AM) {
amOrPm = PM;
} else if (amOrPm == PM){
} else if (amOrPm == PM) {
amOrPm = AM;
}
updateAmPmDisplay(amOrPm);
@@ -339,56 +362,61 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
mTypedTimes = new ArrayList<Integer>();
}
// Set the theme at the end so that the initialize()s above don't counteract the theme.
mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark);
// Prepare some palette to use.
int white = res.getColor(R.color.white);
int circleBackground = res.getColor(R.color.circle_background);
int line = res.getColor(R.color.line_background);
int timeDisplay = res.getColor(R.color.numbers_text_color);
ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color);
int doneBackground = R.drawable.done_background_color;
int darkGray = res.getColor(R.color.dark_gray);
int lightGray = res.getColor(R.color.light_gray);
int darkLine = res.getColor(R.color.line_dark);
ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark);
int darkDoneBackground = R.drawable.done_background_color_dark;
// // Set the theme at the end so that the initialize()s above don't counteract the theme.
// mTimePicker.setTheme(getActivity().getApplicationContext(), mThemeDark);
// // Prepare some palette to use.
// int white = res.getColor(R.color.white);
// int circleBackground = res.getColor(R.color.circle_background);
// int line = res.getColor(R.color.line_background);
// int timeDisplay = res.getColor(R.color.numbers_text_color);
// ColorStateList doneTextColor = res.getColorStateList(R.color.done_text_color);
// int doneBackground = R.drawable.done_background_color;
//
// int darkGray = res.getColor(R.color.dark_gray);
// int lightGray = res.getColor(R.color.light_gray);
// int darkLine = res.getColor(R.color.line_dark);
// ColorStateList darkDoneTextColor = res.getColorStateList(R.color.done_text_color_dark);
// int darkDoneBackground = R.drawable.done_background_color_dark;
// Set the palette for each view based on the theme.
view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white);
view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white);
((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay);
((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay);
view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line);
mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor);
mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground);
// view.findViewById(R.id.time_display_background).setBackgroundColor(mThemeDark? darkGray : white);
// view.findViewById(R.id.time_display).setBackgroundColor(mThemeDark? darkGray : white);
// ((TextView) view.findViewById(R.id.separator)).setTextColor(mThemeDark? white : timeDisplay);
// ((TextView) view.findViewById(R.id.ampm_label)).setTextColor(mThemeDark? white : timeDisplay);
// view.findViewById(R.id.line).setBackgroundColor(mThemeDark? darkLine : line);
// mDoneButton.setTextColor(mThemeDark? darkDoneTextColor : doneTextColor);
// mTimePicker.setBackgroundColor(mThemeDark? lightGray : circleBackground);
// mDoneButton.setBackgroundResource(mThemeDark? darkDoneBackground : doneBackground);
return view;
}
@Override
public void onResume() {
public void onResume()
{
super.onResume();
mHapticFeedbackController.start();
}
@Override
public void onPause() {
public void onPause()
{
super.onPause();
mHapticFeedbackController.stop();
}
public void tryVibrate() {
public void tryVibrate()
{
mHapticFeedbackController.tryVibrate();
}
private void updateAmPmDisplay(int amOrPm) {
private void updateAmPmDisplay(int amOrPm)
{
if (amOrPm == AM) {
mAmPmTextView.setText(mAmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mAmText);
mAmPmHitspace.setContentDescription(mAmText);
} else if (amOrPm == PM){
} else if (amOrPm == PM) {
mAmPmTextView.setText(mPmText);
Utils.tryAccessibilityAnnounce(mTimePicker, mPmText);
mAmPmHitspace.setContentDescription(mPmText);
@@ -398,7 +426,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
@Override
public void onSaveInstanceState(Bundle outState) {
public void onSaveInstanceState(Bundle outState)
{
if (mTimePicker != null) {
outState.putInt(KEY_HOUR_OF_DAY, mTimePicker.getHours());
outState.putInt(KEY_MINUTE, mTimePicker.getMinutes());
@@ -409,6 +438,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
}
outState.putBoolean(KEY_DARK_THEME, mThemeDark);
outState.putInt(KEY_SELECTED_COLOR, mSelectedColor);
}
}
@@ -416,7 +446,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Called by the picker for updating the header display.
*/
@Override
public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance)
{
if (pickerIndex == HOUR_INDEX) {
setHour(newValue, false);
String announcement = String.format("%d", newValue);
@@ -428,7 +459,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
Utils.tryAccessibilityAnnounce(mTimePicker, announcement);
} else if (pickerIndex == MINUTE_INDEX){
} else if (pickerIndex == MINUTE_INDEX) {
setMinute(newValue);
mTimePicker.setContentDescription(mMinutePickerDescription + ": " + newValue);
} else if (pickerIndex == AMPM_INDEX) {
@@ -441,7 +472,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private void setHour(int value, boolean announce) {
private void setHour(int value, boolean announce)
{
String format;
if (mIs24HourMode) {
format = "%02d";
@@ -461,7 +493,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private void setMinute(int value) {
private void setMinute(int value)
{
if (value == 60) {
value = 0;
}
@@ -473,7 +506,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
// Show either Hours or Minutes.
private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
boolean announce) {
boolean announce)
{
mTimePicker.setCurrentItemShowing(index, animateCircle);
TextView labelToAnimate;
@@ -496,8 +530,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
labelToAnimate = mMinuteView;
}
int hourColor = (index == HOUR_INDEX)? mSelectedColor : mUnselectedColor;
int minuteColor = (index == MINUTE_INDEX)? mSelectedColor : mUnselectedColor;
int hourColor = (index == HOUR_INDEX) ? mSelectedColor : mUnselectedColor;
int minuteColor = (index == MINUTE_INDEX) ? mSelectedColor : mUnselectedColor;
mHourView.setTextColor(hourColor);
mMinuteView.setTextColor(minuteColor);
@@ -510,15 +544,17 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* For keyboard mode, processes key events.
*
* @param keyCode the pressed key.
* @return true if the key was successfully processed, false otherwise.
*/
private boolean processKeyUp(int keyCode) {
private boolean processKeyUp(int keyCode)
{
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
dismiss();
return true;
} else if (keyCode == KeyEvent.KEYCODE_TAB) {
if(mInKbMode) {
if (mInKbMode) {
if (isTypedTimeFullyLegal()) {
finishKbMode(true);
}
@@ -533,7 +569,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
if (mCallback != null) {
mCallback.onTimeSet(mTimePicker,
mTimePicker.getHours(), mTimePicker.getMinutes());
mTimePicker.getHours(), mTimePicker.getMinutes());
}
dismiss();
return true;
@@ -550,7 +586,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
}
Utils.tryAccessibilityAnnounce(mTimePicker,
String.format(mDeletedKeyFormat, deletedKeyStr));
String.format(mDeletedKeyFormat, deletedKeyStr));
updateDisplay(true);
}
}
@@ -560,7 +596,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
|| keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
|| keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
|| (!mIs24HourMode &&
(keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
(keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
if (!mInKbMode) {
if (mTimePicker == null) {
// Something's wrong, because time picker should definitely not be null.
@@ -583,11 +619,13 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Try to start keyboard mode with the specified key, as long as the timepicker is not in the
* middle of a touch-event.
*
* @param keyCode The key to use as the first press. Keyboard mode will not be started if the
* key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
* key.
* key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
* key.
*/
private void tryStartingKbMode(int keyCode) {
private void tryStartingKbMode(int keyCode)
{
if (mTimePicker.trySettingInputEnabled(false) &&
(keyCode == -1 || addKeyIfLegal(keyCode))) {
mInKbMode = true;
@@ -596,7 +634,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private boolean addKeyIfLegal(int keyCode) {
private boolean addKeyIfLegal(int keyCode)
{
// If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
// we'll need to see if AM/PM have been typed.
if ((mIs24HourMode && mTypedTimes.size() == 4) ||
@@ -628,7 +667,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Traverse the tree to see if the keys that have been typed so far are legal as is,
* or may become legal as more keys are typed (excluding backspace).
*/
private boolean isTypedTimeLegalSoFar() {
private boolean isTypedTimeLegalSoFar()
{
Node node = mLegalTimesTree;
for (int keyCode : mTypedTimes) {
node = node.canReach(keyCode);
@@ -642,7 +682,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Check if the time that has been typed so far is completely legal, as is.
*/
private boolean isTypedTimeFullyLegal() {
private boolean isTypedTimeFullyLegal()
{
if (mIs24HourMode) {
// For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
// getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
@@ -656,7 +697,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private int deleteLastTypedKey() {
private int deleteLastTypedKey()
{
int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
if (!isTypedTimeFullyLegal()) {
mDoneButton.setEnabled(false);
@@ -666,9 +708,11 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
*
* @param changeDisplays If true, update the displays with the relevant time.
*/
private void finishKbMode(boolean updateDisplays) {
private void finishKbMode(boolean updateDisplays)
{
mInKbMode = false;
if (!mTypedTimes.isEmpty()) {
int values[] = getEnteredTime(null);
@@ -688,29 +732,31 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
* empty, either show an empty display (filled with the placeholder text), or update from the
* timepicker's values.
*
* @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
* Otherwise, revert to the timepicker's values.
* Otherwise, revert to the timepicker's values.
*/
private void updateDisplay(boolean allowEmptyDisplay) {
private void updateDisplay(boolean allowEmptyDisplay)
{
if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
int hour = mTimePicker.getHours();
int minute = mTimePicker.getMinutes();
setHour(hour, true);
setMinute(minute);
if (!mIs24HourMode) {
updateAmPmDisplay(hour < 12? AM : PM);
updateAmPmDisplay(hour < 12 ? AM : PM);
}
setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true);
mDoneButton.setEnabled(true);
} else {
Boolean[] enteredZeros = {false, false};
int[] values = getEnteredTime(enteredZeros);
String hourFormat = enteredZeros[0]? "%02d" : "%2d";
String minuteFormat = (enteredZeros[1])? "%02d" : "%2d";
String hourStr = (values[0] == -1)? mDoublePlaceholderText :
String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
String minuteStr = (values[1] == -1)? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
mHourView.setText(hourStr);
mHourSpaceView.setText(hourStr);
mHourView.setTextColor(mUnselectedColor);
@@ -723,7 +769,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private static int getValFromKeyCode(int keyCode) {
private static int getValFromKeyCode(int keyCode)
{
switch (keyCode) {
case KeyEvent.KEYCODE_0:
return 0;
@@ -752,20 +799,22 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Get the currently-entered time, as integer values of the hours and minutes typed.
*
* @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
* may then be used for the caller to know whether zeros had been explicitly entered as either
* hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
* may then be used for the caller to know whether zeros had been explicitly entered as either
* hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
* @return A size-3 int array. The first value will be the hours, the second value will be the
* minutes, and the third will be either TimePickerDialog.AM or TimePickerDialog.PM.
*/
private int[] getEnteredTime(Boolean[] enteredZeros) {
private int[] getEnteredTime(Boolean[] enteredZeros)
{
int amOrPm = -1;
int startIndex = 1;
if (!mIs24HourMode && isTypedTimeFullyLegal()) {
int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
if (keyCode == getAmOrPmKeyCode(AM)) {
amOrPm = AM;
} else if (keyCode == getAmOrPmKeyCode(PM)){
} else if (keyCode == getAmOrPmKeyCode(PM)) {
amOrPm = PM;
}
startIndex = 2;
@@ -776,15 +825,15 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
if (i == startIndex) {
minute = val;
} else if (i == startIndex+1) {
minute += 10*val;
} else if (i == startIndex + 1) {
minute += 10 * val;
if (enteredZeros != null && val == 0) {
enteredZeros[1] = true;
}
} else if (i == startIndex+2) {
} else if (i == startIndex + 2) {
hour = val;
} else if (i == startIndex+3) {
hour += 10*val;
} else if (i == startIndex + 3) {
hour += 10 * val;
if (enteredZeros != null && val == 0) {
enteredZeros[0] = true;
}
@@ -798,7 +847,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Get the keycode value for AM and PM in the current language.
*/
private int getAmOrPmKeyCode(int amOrPm) {
private int getAmOrPmKeyCode(int amOrPm)
{
// Cache the codes.
if (mAmKeyCode == -1 || mPmKeyCode == -1) {
// Find the first character in the AM/PM text that is unique.
@@ -833,7 +883,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
/**
* Create a tree for deciding what keys can legally be typed.
*/
private void generateLegalTimesTree() {
private void generateLegalTimesTree()
{
// Create a quick cache of numbers to their keycodes.
int k0 = KeyEvent.KEYCODE_0;
int k1 = KeyEvent.KEYCODE_1;
@@ -889,7 +940,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
// When the first digit is 2, the second digit may be 4-5.
secondDigit = new Node(k4, k5);
firstDigit.addChild(secondDigit);
// We must now be followd by the last minute digit. E.g. 2:40, 2:53.
// We must now be followed by the last minute digit. E.g. 2:40, 2:53.
secondDigit.addChild(minuteSecondDigit);
// The first digit may be 3-9.
@@ -966,20 +1017,24 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
* mLegalKeys represents the keys that can be typed to get to the node.
* mChildren are the children that can be reached from this node.
*/
private class Node {
private class Node
{
private int[] mLegalKeys;
private ArrayList<Node> mChildren;
public Node(int... legalKeys) {
public Node(int... legalKeys)
{
mLegalKeys = legalKeys;
mChildren = new ArrayList<Node>();
}
public void addChild(Node child) {
public void addChild(Node child)
{
mChildren.add(child);
}
public boolean containsKey(int key) {
public boolean containsKey(int key)
{
for (int i = 0; i < mLegalKeys.length; i++) {
if (mLegalKeys[i] == key) {
return true;
@@ -988,7 +1043,8 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
return false;
}
public Node canReach(int key) {
public Node canReach(int key)
{
if (mChildren == null) {
return null;
}
@@ -1001,13 +1057,28 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue
}
}
private class KeyboardListener implements OnKeyListener {
private class KeyboardListener implements OnKeyListener
{
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
public boolean onKey(View v, int keyCode, KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_UP) {
return processKeyUp(keyCode);
}
return false;
}
}
public void setDismissListener(DialogInterface.OnDismissListener listener)
{
dismissListener = listener;
}
@Override
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
if (dismissListener != null)
dismissListener.onDismiss(dialog);
}
}

View File

@@ -49,33 +49,33 @@
android:layout_height="1dip"
android:background="@color/line_background" />
<LinearLayout
<androidx.appcompat.widget.LinearLayoutCompat
style="?android:attr/buttonBarStyle"
android:layout_width="@dimen/date_picker_component_width"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
<androidx.appcompat.widget.AppCompatButton
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/clear_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/done_background_color"
android:minHeight="48dp"
android:textColor="#333"
android:text="@string/clear_label"
android:textColor="@color/done_text_color"
android:textSize="@dimen/done_label_size" />
<Button
<androidx.appcompat.widget.AppCompatButton
style="?android:attr/buttonBarButtonStyle"
android:id="@+id/done_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/done_background_color"
android:minHeight="48dp"
android:textColor="#333"
android:text="@string/done_label"
android:textColor="@color/done_text_color"
android:textSize="@dimen/done_label_size" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</LinearLayout>

View File

@@ -24,16 +24,36 @@
<dimen name="color_swatch_margins_large">8dip</dimen>
<dimen name="color_swatch_margins_small">4dip</dimen>
<item name="circle_radius_multiplier" format="float" type="string" translatable="false">0.82</item>
<item name="circle_radius_multiplier_24HourMode" format="float" type="string" translatable="false">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string" translatable="false">0.16</item>
<item name="ampm_circle_radius_multiplier" format="float" type="string" translatable="false">0.19</item>
<item name="numbers_radius_multiplier_normal" format="float" type="string" translatable="false">0.81</item>
<item name="numbers_radius_multiplier_inner" format="float" type="string" translatable="false">0.60</item>
<item name="numbers_radius_multiplier_outer" format="float" type="string" translatable="false">0.83</item>
<item name="text_size_multiplier_normal" format="float" type="string" translatable="false">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string" translatable="false">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string" translatable="false">0.11</item>
<item name="circle_radius_multiplier" format="float" translatable="false" type="string">
0.82
</item>
<item name="circle_radius_multiplier_24HourMode" format="float" translatable="false" type="string">
0.85
</item>
<item name="selection_radius_multiplier" format="float" translatable="false" type="string">
0.16
</item>
<item name="ampm_circle_radius_multiplier" format="float" translatable="false" type="string">
0.19
</item>
<item name="numbers_radius_multiplier_normal" format="float" translatable="false" type="string">
0.81
</item>
<item name="numbers_radius_multiplier_inner" format="float" translatable="false" type="string">
0.60
</item>
<item name="numbers_radius_multiplier_outer" format="float" translatable="false" type="string">
0.83
</item>
<item name="text_size_multiplier_normal" format="float" translatable="false" type="string">
0.17
</item>
<item name="text_size_multiplier_inner" format="float" translatable="false" type="string">
0.14
</item>
<item name="text_size_multiplier_outer" format="float" translatable="false" type="string">
0.11
</item>
<dimen name="time_label_size">60sp</dimen>
<dimen name="extra_time_label_margin">-30dp</dimen>
@@ -64,8 +84,8 @@
<dimen name="year_label_height">64dp</dimen>
<dimen name="year_label_text_size">22dp</dimen>
<string name="color_swatch_description" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string>
<string name="color_swatch_description_selected" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string>
<string name="color_swatch_description" translatable="false">Color <xliff:g example="14" id="color_index">%1$d</xliff:g></string>
<string name="color_swatch_description_selected" translatable="false">Color <xliff:g example="14" id="color_index">%1$d</xliff:g> selected</string>
<!-- Date and time picker -->
<string name="hour_picker_description" translatable="false">Hours circular slider</string>
@@ -74,11 +94,56 @@
<string name="year_picker_description" translatable="false">Year list</string>
<string name="select_day" translatable="false">Select month and day</string>
<string name="select_year" translatable="false">Select year</string>
<string name="item_is_selected" translatable="false"><xliff:g id="item" example="2013">%1$s</xliff:g> selected</string>
<string name="deleted_key" translatable="false"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string>
<string name="item_is_selected" translatable="false"><xliff:g example="2013" id="item">%1$s</xliff:g> selected</string>
<string name="deleted_key" translatable="false"><xliff:g example="4" id="key">%1$s</xliff:g> deleted</string>
<string name="time_placeholder" translatable="false">--</string>
<string name="time_separator" translatable="false">:</string>
<string name="radial_numbers_typeface" translatable="false">sans-serif</string>
<string name="sans_serif" translatable="false">sans-serif</string>
<string name="day_of_week_label_typeface" translatable="false">sans-serif</string>
<!-- Time and Date picker -->
<color name="circle_background">#f2f2f2</color>
<color name="line_background">#cccccc</color>
<color name="ampm_text_color">#8c8c8c</color>
<color name="done_text_color_normal">#000000</color>
<color name="done_text_color_disabled">#cccccc</color>
<color name="numbers_text_color">#8c8c8c</color>
<color name="transparent">#00000000</color>
<color name="transparent_black">#7f000000</color>
<color name="blue">#33b5e5</color>
<color name="blue_focused">#c1e8f7</color>
<color name="neutral_pressed">#33999999</color>
<color name="darker_blue">#0099cc</color>
<color name="date_picker_text_normal">#ff999999</color>
<color name="calendar_header">#999999</color>
<color name="date_picker_view_animator">#f2f2f2</color>
<color name="calendar_selected_date_text">#ffd1d2d4</color>
<color name="done_text_color">#888888</color>
<color name="done_text_color_dark">#888888</color>
<color name="white">#ffffff</color>
<color name="black">#000000</color>
<!-- Colors for red theme -->
<color name="red">#ff3333</color>
<color name="red_focused">#853333</color>
<color name="light_gray">#404040</color>
<color name="dark_gray">#363636</color>
<color name="line_dark">#808080</color>
<style name="time_label">
<item name="android:textSize">@dimen/time_label_size</item>
<item name="android:textColor">@color/numbers_text_color</item>
</style>
<style name="ampm_label">
<item name="android:textSize">@dimen/ampm_label_size</item>
<item name="android:textAllCaps">true</item>
<item name="android:textColor">@color/ampm_text_color</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TimePickerDialog" parent="@style/Theme.AppCompat.Light.Dialog">
<item name="windowNoTitle">true</item>
</style>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="select_hours"/>
<string name="select_minutes"/>
<string name="color_picker_default_title"/>
<string name="clear"/>
<string name="clear_label"/>
<string name="done_label"/>
</resources>

21
android/build.gradle Normal file
View File

@@ -0,0 +1,21 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$BUILD_TOOLS_VERSION"
classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "org.ajoberstar:grgit:1.5.0"
}
}
allprojects {
repositories {
google()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
jcenter()
}
}

283
android/build.sh Executable file
View File

@@ -0,0 +1,283 @@
#!/bin/bash
# Copyright (C) 2017 Á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/>.
cd "$(dirname "$0")"
ADB="${ANDROID_HOME}/platform-tools/adb"
EMULATOR="${ANDROID_HOME}/tools/emulator"
GRADLE="./gradlew --stacktrace"
PACKAGE_NAME=org.isoron.uhabits
OUTPUTS_DIR=uhabits-android/build/outputs
VERSION=$(cat gradle.properties | grep VERSION_NAME | sed -e 's/.*=//g;s/ //g')
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
echo "Error: ANDROID_HOME is not set correctly"
exit 1
fi
log_error() {
if [ ! -z "$TEAMCITY_VERSION" ]; then
echo "###teamcity[progressMessage '$1']"
else
local COLOR='\033[1;31m'
local NC='\033[0m'
echo -e "$COLOR>>> $1 $NC"
fi
}
log_info() {
if [ ! -z "$TEAMCITY_VERSION" ]; then
echo "###teamcity[progressMessage '$1']"
else
local COLOR='\033[1;32m'
local NC='\033[0m'
echo -e "$COLOR>>> $1 $NC"
fi
}
fail() {
log_error "BUILD FAILED"
exit 1
}
if [ ! -z $RELEASE ]; then
log_info "Reading secret env variables from ../.secret/env"
source ../.secret/env || fail
fi
run_adb_as_root() {
log_info "Running adb as root"
$ADB root
}
build_apk() {
log_info "Removing old APKs..."
rm -vf build/*.apk
if [ ! -z $RELEASE ]; then
log_info "Building release APK"
$GRADLE assembleRelease
cp -v uhabits-android/build/outputs/apk/release/uhabits-android-release.apk build/loop-$VERSION-release.apk
fi
log_info "Building debug APK"
$GRADLE assembleDebug --stacktrace || fail
cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk
}
build_instrumentation_apk() {
log_info "Building instrumentation APK"
if [ ! -z $RELEASE ]; then
$GRADLE assembleAndroidTest \
-Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \
-Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \
-Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \
-Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail
else
$GRADLE assembleAndroidTest || fail
fi
}
uninstall_apk() {
log_info "Uninstalling existing APK"
$ADB uninstall ${PACKAGE_NAME}
}
install_test_butler() {
log_info "Installing Test Butler"
$ADB uninstall com.linkedin.android.testbutler
$ADB install tools/test-butler-app-2.0.2.apk
}
install_apk() {
log_info "Installing APK"
if [ ! -z $RELEASE ]; then
$ADB install -r ${OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail
else
$ADB install -t -r ${OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail
fi
}
install_test_apk() {
log_info "Uninstalling existing test APK"
$ADB uninstall ${PACKAGE_NAME}.test
log_info "Installing test APK"
$ADB install -r ${OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail
}
run_instrumented_tests() {
SIZE=$1
log_info "Running instrumented tests"
$ADB shell am instrument \
-r -e coverage true -e size $SIZE \
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
| tee ${OUTPUTS_DIR}/instrument.txt
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $OUTPUTS_DIR/instrument.txt; then
log_error "Some instrumented tests failed"
fetch_images
fetch_logcat
exit 1
fi
#mkdir -p ${OUTPUTS_DIR}/code-coverage/connected/
#$ADB pull /data/user/0/${PACKAGE_NAME}/files/coverage.ec \
# ${OUTPUTS_DIR}/code-coverage/connected/ \
# || log_error "COVERAGE REPORT NOT AVAILABLE"
}
parse_instrumentation_results() {
log_info "Parsing instrumented test results"
java -jar tools/automator-log-converter-1.5.0.jar ${OUTPUTS_DIR}/instrument.txt || fail
}
generate_coverage_badge() {
log_info "Generating code coverage badge"
CORE_REPORT=uhabits-core/build/reports/jacoco/test/jacocoTestReport.xml
rm -f ${OUTPUTS_DIR}/coverage-badge.svg
python3 tools/coverage-badge/badge.py -i $CORE_REPORT -o ${OUTPUTS_DIR}/coverage-badge
}
fetch_logcat() {
log_info "Fetching logcat"
$ADB logcat -d > ${OUTPUTS_DIR}/logcat.txt
}
run_jvm_tests() {
log_info "Running JVM tests"
if [ ! -z $RELEASE ]; then
$GRADLE testReleaseUnitTest :uhabits-core:check || fail
else
$GRADLE testDebugUnitTest :uhabits-core:check || fail
fi
}
uninstall_test_apk() {
log_info "Uninstalling test APK"
$ADB uninstall ${PACKAGE_NAME}.test
}
fetch_images() {
log_info "Fetching images"
rm -rf $OUTPUTS_DIR/test-screenshots
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $OUTPUTS_DIR
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
}
accept_images() {
find $OUTPUTS_DIR/test-screenshots -name '*.expected*' -delete
rsync -av $OUTPUTS_DIR/test-screenshots/ uhabits-android/src/androidTest/assets/
}
run_tests() {
SIZE=$1
run_adb_as_root
install_test_butler
uninstall_apk
install_apk
install_test_apk
run_instrumented_tests $SIZE
parse_instrumentation_results
fetch_logcat
uninstall_test_apk
}
parse_opts() {
OPTS=`getopt -o r --long release -n 'build.sh' -- "$@"`
if [ $? != 0 ] ; then exit 1; fi
eval set -- "$OPTS"
while true; do
case "$1" in
-r | --release ) RELEASE=1; shift ;;
* ) break ;;
esac
done
}
remove_build_dir() {
rm -rfv .gradle
rm -rfv build
rm -rfv android-base/build
rm -rfv android-pickers/build
rm -rfv uhabits-android/build
rm -rfv uhabits-core/build
}
case "$1" in
build)
shift; parse_opts $*
build_apk
build_instrumentation_apk
run_jvm_tests
#generate_coverage_badge
;;
medium-tests)
shift; parse_opts $*
for attempt in {1..3}; do
(run_tests medium) && exit 0
done
exit 1
;;
large-tests)
shift; parse_opts $*
run_tests large
;;
fetch-images)
fetch_images
;;
accept-images)
accept_images
;;
install)
shift; parse_opts $*
build_apk
install_apk
;;
clean)
remove_build_dir
;;
*)
cat <<END
Usage: $0 <command> [options]
Builds, installs and tests Loop Habit Tracker
Commands:
accept-images Copies fetched images to corresponding assets folder
build Build APK and run JVM tests
clean Remove build directory
fetch-images Fetches failed view test images from device
install Install app on connected device
large-tests Run large-sized tests on connected device
medium-tests Run medium-sized tests on connected device
Options:
-r --release Build and test release APK, instead of debug
END
exit 1
;;
esac

20
android/gradle.properties Normal file
View File

@@ -0,0 +1,20 @@
VERSION_CODE = 20000
VERSION_NAME = 2.0.0-alpha
MIN_SDK_VERSION = 23
TARGET_SDK_VERSION = 29
COMPILE_SDK_VERSION = 29
DAGGER_VERSION = 2.25.4
KOTLIN_VERSION = 1.4.0
KX_COROUTINES_VERSION = 1.4.2
SUPPORT_LIBRARY_VERSION = 28.0.0
AUTO_FACTORY_VERSION = 1.0-beta6
BUILD_TOOLS_VERSION = 4.1.0
KTOR_VERSION=1.4.2
org.gradle.parallel=false
org.gradle.daemon=true
org.gradle.jvmargs=-Xms2048m -Xmx2048m -XX:MaxPermSize=2048m
android.useAndroidX=true
android.enableJetifier=true

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Fri Mar 17 21:42:38 EDT 2017
#Sat Nov 28 09:55:24 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

183
android/gradlew vendored Executable file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
android/play Symbolic link
View File

@@ -0,0 +1 @@
uhabits-android/src/main/play/

1
android/settings.gradle Normal file
View File

@@ -0,0 +1 @@
include ':uhabits-android', ':uhabits-core', ':android-base', ':android-pickers'

Binary file not shown.

View File

@@ -0,0 +1,157 @@
"""
Generate coverage badges for Coverage.py.
Forked from https://github.com/dbrgn/coverage-badge
"""
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals
import os
import sys
import argparse
import pkg_resources
from bs4 import BeautifulSoup
__version__ = '0.2.0-uhabits'
DEFAULT_COLOR = '#a4a61d'
COLORS = {
'brightgreen': '#4c1',
'green': '#97CA00',
'yellowgreen': '#a4a61d',
'yellow': '#dfb317',
'orange': '#fe7d37',
'red': '#e05d44',
'lightgrey': '#9f9f9f',
}
COLOR_RANGES = [
(95, 'brightgreen'),
(90, 'green'),
(75, 'yellowgreen'),
(60, 'yellow'),
(40, 'orange'),
(0, 'red'),
]
class Devnull(object):
"""
A file like object that does nothing.
"""
def write(self, *args, **kwargs):
pass
def get_total(report):
missed = 0
covered = 0
for r in report.split(":"):
doc = BeautifulSoup(open(r), 'xml')
tag = doc.select("report > counter[type^INST]")[0]
missed = missed + float(tag['missed'])
covered = covered + float(tag['covered'])
return str(int(round(100 * covered / (missed + covered))))
def get_color(total):
"""
Return color for current coverage percent
"""
try:
xtotal = int(total)
except ValueError:
return COLORS['lightgrey']
for range_, color in COLOR_RANGES:
if xtotal >= range_:
return COLORS[color]
def get_badge(total, color=DEFAULT_COLOR):
"""
Read the SVG template from the package, update total, return SVG as a
string.
"""
template_path = os.path.join('templates', 'flat.svg')
template = pkg_resources.resource_string(__name__, template_path).decode('utf8')
return template.replace('{{ total }}', total).replace('{{ color }}', color)
def parse_args(argv=None):
"""
Parse the command line arguments.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-o', dest='filepath',
help='Save the file to the specified path.')
parser.add_argument('-p', dest='plain_color', action='store_true',
help='Plain color mode. Standard green badge.')
parser.add_argument('-f', dest='force', action='store_true',
help='Force overwrite image, use with -o key.')
parser.add_argument('-q', dest='quiet', action='store_true',
help='Don\'t output any non-error messages.')
parser.add_argument('-v', dest='print_version', action='store_true',
help='Show version.')
parser.add_argument('-i', dest='reportFilename',
help='Jacoco report')
# If arguments have been passed in, use them.
if argv:
return parser.parse_args(argv)
# Otherwise, just use sys.argv directly.
else:
return parser.parse_args()
def save_badge(badge, filepath, force=False):
"""
Save badge to the specified path.
"""
# Validate path (part 1)
if filepath.endswith('/'):
print('Error: Filepath may not be a directory.')
sys.exit(1)
# Get absolute filepath
path = os.path.abspath(filepath)
if not path.lower().endswith('.svg'):
path += '.svg'
# Validate path (part 2)
if not force and os.path.exists(path):
print('Error: "{}" already exists.'.format(path))
sys.exit(1)
# Write file
with open(path, 'w') as f:
f.write(badge)
return path
def main(argv=None):
"""
Console scripts entry point.
"""
args = parse_args(argv)
# Print version
if args.print_version:
print('coverage-badge v{}'.format(__version__))
sys.exit(0)
total = get_total(args.reportFilename)
color = DEFAULT_COLOR if args.plain_color else get_color(total)
badge = get_badge(total, color)
# Show or save output
if args.filepath:
path = save_badge(badge, args.filepath, args.force)
if not args.quiet:
print('Saved badge to {}'.format(path))
else:
print(badge, end='')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="99" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="#555" d="M0 0h63v20H0z"/>
<path fill="{{ color }}" d="M63 0h36v20H63z"/>
<path fill="url(#b)" d="M0 0h99v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="31.5" y="14">coverage</text>
<text x="80" y="15" fill="#010101" fill-opacity=".3">{{ total }}%</text>
<text x="80" y="14">{{ total }}%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

View File

@@ -0,0 +1,156 @@
plugins {
id 'idea'
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.github.triplet.play' version '2.6.2'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion COMPILE_SDK_VERSION as Integer
def secretPropsFile = file("../../.secret/gradle.properties")
if (secretPropsFile.exists()) {
def secrets = new Properties()
secretPropsFile.withInputStream { secrets.load(it) }
signingConfigs {
release {
storeFile file(secrets.LOOP_KEY_STORE)
storePassword secrets.LOOP_STORE_PASSWORD
keyAlias secrets.LOOP_KEY_ALIAS
keyPassword secrets.LOOP_KEY_PASSWORD
}
}
buildTypes.release.signingConfig signingConfigs.release
}
defaultConfig {
versionCode VERSION_CODE as Integer
versionName "$VERSION_NAME"
minSdkVersion MIN_SDK_VERSION as Integer
targetSdkVersion TARGET_SDK_VERSION as Integer
applicationId "org.isoron.uhabits"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
testCoverageEnabled true
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
disable 'GoogleAppIndexingWarning'
}
compileOptions {
coreLibraryDesugaringEnabled true
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen { false }
showStandardStreams = true
}
}
}
sourceSets {
main.assets.srcDirs += '../uhabits-core/src/main/resources/'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(":uhabits-core")
implementation project(":android-base")
implementation project(":android-pickers")
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.github.paolorotolo:appintro:3.4.0"
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
implementation "com.jakewharton:butterknife:8.6.1-SNAPSHOT"
implementation "org.apmem.tools:layouts:1.10"
implementation "com.google.code.gson:gson:2.8.5"
implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KX_COROUTINES_VERSION"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$KX_COROUTINES_VERSION"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
implementation 'com.google.zxing:core:3.4.1'
implementation "io.ktor:ktor-client-core:$KTOR_VERSION"
implementation "io.ktor:ktor-client-android:$KTOR_VERSION"
implementation "io.ktor:ktor-client-json:$KTOR_VERSION"
implementation "io.ktor:ktor-client-jackson:$KTOR_VERSION"
implementation "com.google.guava:guava:30.0-android"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
compileOnly "javax.annotation:jsr250-api:1.0"
compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
kapt "com.jakewharton:butterknife-compiler:10.2.1"
annotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation "com.google.dagger:dagger:$DAGGER_VERSION"
androidTestImplementation "com.linkedin.testbutler:test-butler-library:1.3.1"
androidTestCompileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
androidTestAnnotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
androidTestImplementation 'androidx.annotation:annotation:1.0.0'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation "io.ktor:ktor-client-mock:$KTOR_VERSION"
androidTestImplementation "io.ktor:ktor-jackson:$KTOR_VERSION"
androidTestImplementation project(":uhabits-core")
kaptAndroidTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
// mockito-android 2+ includes net.bytebuddy, which causes tests to fail.
// Excluding the package net.bytebuddy on AndroidManifest.xml breaks some
// AndroidJUnitRunner functionality, such as running individual methods.
androidTestImplementation "org.mockito:mockito-core:1.10.19"
androidTestImplementation "com.google.dexmaker:dexmaker-mockito:1.2"
testImplementation "com.google.dagger:dagger:$DAGGER_VERSION"
testImplementation "org.mockito:mockito-core:2.8.9"
testImplementation "org.mockito:mockito-inline:2.8.9"
testImplementation "junit:junit:4.12"
implementation('com.opencsv:opencsv:3.10') {
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation('io.socket:socket.io-client:0.8.3') {
exclude group: 'org.json', module: 'json'
}
}
kapt {
correctErrorTypes = true
}
play {
serviceAccountCredentials = file("../../.secret/gcp-key.json")
track = "alpha"
}

View File

@@ -0,0 +1,31 @@
-dontobfuscate
-dontwarn java.**
-dontwarn javax.**
-dontwarn org.apache.commons.beanutils.*
-dontwarn org.codehaus.mojo.**
-dontnote com.android.**
-dontnote com.google.gson.internal.**
-dontnote dagger.*
-dontnote dalvik.system.**
-dontnote javax.inject.**
-dontnote org.apache.harmony.xnet.**
-dontnote org.isoron.**
-dontnote sun.misc.**
-dontnote sun.security.**
-keep class com.getpebble.** { *; }
-keep class com.github.paolorotolo.** { *; }
-keep class io.socket.** { *; }
-keep class okhttp3.** { *; }
-keep class okio.** { *; }
-keep class org.isoron.** { *; }
-keep class sun.misc.Unsafe { *; }
-keep class android.support.test.** { *; }
-keep class org.mockito.** { *; }
-keep class org.junit.** { *; }
-keep class kotlin.** { *; }
-dontskipnonpubliclibraryclassmembers

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