Compare commits
709 Commits
v1.7.8
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 16b0682229 | |||
| a77798f293 | |||
| 08050ff616 | |||
| 12b080152b | |||
| df3d660e83 | |||
| 4908709296 | |||
| 5717ae1bf1 | |||
| 872c8d9d81 | |||
| 0b6110f0f9 | |||
| 6df5e9ebe9 | |||
| 2b9fd74a1d | |||
| 4a4b3c6aeb | |||
| 7979f74bea | |||
| b0336fb495 | |||
| 328fcd23f4 | |||
| 9c0951ae58 | |||
| 3f51561271 | |||
| 1787c0e74e | |||
| 49faacda1c | |||
| 339eeff1ff | |||
| 849212fd2f | |||
| 356b2b06e4 | |||
| b6eefbdb36 | |||
| 2a72601153 | |||
| 2bf7358207 | |||
| 8c6e2ef461 | |||
| ce0cbb6ee2 | |||
| 67ef3bb90c | |||
| b82af419f8 | |||
| 49ff9a7edf | |||
| 53ebdf4f14 | |||
| 4762b54701 | |||
| aa288ac406 | |||
| 2228dbf0f4 | |||
| 9ca1c8e459 | |||
| 61414d62f4 | |||
| f97fed3b9b | |||
| d45281d137 | |||
| 1bb6ad41b2 | |||
| 56c180183e | |||
| af8d983cca | |||
| 0a49232ebd | |||
| cff8e26428 | |||
| e892bccb32 | |||
| 68ccf37fd5 | |||
| 659c528744 | |||
| b1560dd694 | |||
| 0de86ac66c | |||
| 06e5d517cc | |||
| 35ca041bc2 | |||
| 576a334dc9 | |||
| 294aee5d12 | |||
| 0859cec853 | |||
| 23f2978a64 | |||
| a2400172e2 | |||
| 5376f4bff8 | |||
| 0497890cb0 | |||
| 2848c4e77b | |||
| 8fa3ba1b18 | |||
| b4f36dd258 | |||
| 008902d3b7 | |||
| 4764c07f3b | |||
| 8f0cfa8614 | |||
| 865e1969e6 | |||
|
|
bfddc42f5e | ||
| dc0b8deccf | |||
| b674d14b49 | |||
| d594d3b085 | |||
| bef85bf93a | |||
| 76eaefc95b | |||
| 83c1ab35d5 | |||
| 7a6563736a | |||
| 55c50c1119 | |||
| ba08968600 | |||
|
|
2d488a67f2 | ||
|
|
d997b1378d | ||
| 720f98f9bd | |||
| ddea9e78a9 | |||
| c429cb41c0 | |||
| ae286cec14 | |||
| 31d631b155 | |||
| 20142d5f94 | |||
| ef186d55c6 | |||
| 8b847ae9fa | |||
|
|
a4ef657897 | ||
| f44556e281 | |||
| 8a895b2d20 | |||
| 61f32449dd | |||
|
|
07f8583c3d | ||
|
|
69f11c9d4e | ||
|
|
1ffc079042 | ||
| 5fa3f412c0 | |||
| b72cad5316 | |||
|
|
d59ab89426 | ||
| ea019321e6 | |||
| 6967def950 | |||
| 6d4cac427f | |||
| 152b2d5427 | |||
| 9d28fbe7b5 | |||
| c846dfc75a | |||
| ee7eb4ef51 | |||
|
|
57bfe3d801 | ||
| a5ee96f988 | |||
| 7b0eddeac5 | |||
| e8e52db9b1 | |||
| cddbf558e6 | |||
| 79459c373e | |||
| 84523869e8 | |||
| 590298bf5b | |||
| 8067fd5313 | |||
|
|
d2dc756a34 | ||
| 2af1dbf3a6 | |||
| ebab6f08ee | |||
| 4a2b21855a | |||
| 42d5edec26 | |||
| f368e43158 | |||
| 09eb8c9f4d | |||
| 209e709163 | |||
| d20a2be7e6 | |||
| bd68f8fc5a | |||
|
|
1a05f7d85d | ||
|
|
d9ff429c28 | ||
| 3554895a5d | |||
| 16491c142a | |||
| 859fea5ff5 | |||
| 34c73e89db | |||
| 09bf49a9ce | |||
| 48e43869c7 | |||
| 963fb58309 | |||
| 38fb37cde2 | |||
| d0b4e3e163 | |||
| 3ef3be4d16 | |||
| bae0e3bcc1 | |||
| 3e99d821a5 | |||
|
|
acb5051eec | ||
|
|
b76882dd1d | ||
|
|
978946baab | ||
|
|
d202f14c14 | ||
|
|
17a85e517a | ||
|
|
c5bc5deff0 | ||
|
|
b7f04957a5 | ||
|
|
b0f5f96eee | ||
|
|
fd76a3c6fd | ||
| b31482881b | |||
|
|
87231d7fa4 | ||
|
|
4d18a1335c | ||
|
|
424a417a13 | ||
|
|
96d23bdf22 | ||
| a8e77b8df8 | |||
| 5413569ce3 | |||
| d80b85ac8c | |||
| 40bc35935f | |||
| a6060f468d | |||
| 6ec9d51a1e | |||
| de28a5e74e | |||
| 3ba503604b | |||
| 6d48b53861 | |||
| 0cce6b30b1 | |||
| bf650a7565 | |||
| b78cd1dd0d | |||
| 6d9ad8c56c | |||
| e7a3f0cffa | |||
| 6aa72caf6c | |||
| 0b7697d172 | |||
| b9850fa085 | |||
| ecb3978bdd | |||
| fc57a9db6c | |||
| 6c9c2a6c1a | |||
| 3e0529d515 | |||
| 7d8d89fbbd | |||
| c43f3c2fd7 | |||
| 403ed8b250 | |||
| 9ccb2b2737 | |||
| 424a282847 | |||
| 309b6cbcaf | |||
| 72ad14119a | |||
|
|
652ed50d09 | ||
| 6070a7af2e | |||
| 8fd8c2802b | |||
| 923b923745 | |||
| 59c8031372 | |||
| 0058089e7d | |||
| d5a840388c | |||
| 4b07d7d5b1 | |||
| 2fffc25128 | |||
| 4a4501276c | |||
|
|
c8e3735dd6 | ||
|
|
61267e40e7 | ||
|
|
c0b664e1e4 | ||
|
|
e57c319658 | ||
|
|
e54ba826b3 | ||
|
|
9b8784b4c4 | ||
|
|
51a7b7a7d4 | ||
|
|
d761b474cf | ||
|
|
51be585b9d | ||
|
|
76be5037fd | ||
| aee0da2c64 | |||
| f1610e6603 | |||
| a7a1766809 | |||
| 5f83314d56 | |||
| c784f40c55 | |||
| 6a172d135b | |||
| 13f4981066 | |||
|
|
849b91dde2 | ||
|
|
66b4c48d92 | ||
|
|
2e64da4cac | ||
| 323ddcc11a | |||
| 175000efd1 | |||
| 6f94fc48c1 | |||
|
|
18d1d0d9f7 | ||
|
|
47edea47ae | ||
|
|
1714cf8050 | ||
|
|
7366e9a47f | ||
|
|
e58589cfbd | ||
|
|
2999e0e5eb | ||
|
|
fa7bc27124 | ||
|
|
f5be9d3c67 | ||
|
|
2c46e8909a | ||
|
|
8b042f30dc | ||
|
|
46761926d2 | ||
|
|
88d6a8e513 | ||
|
|
557ae19297 | ||
|
|
9c10a56dda | ||
|
|
895b068321 | ||
|
|
fb98c5fe9a | ||
|
|
0ec604f21e | ||
|
|
bcd9dd1bb5 | ||
|
|
61bcd253f8 | ||
|
|
fb40dbdabc | ||
|
|
0990192cd6 | ||
|
|
1cf2d69534 | ||
| a3344358b9 | |||
| 22fcecb48c | |||
| 58d8c799ce | |||
| f9437d61b0 | |||
|
|
0489dc39e0 | ||
|
|
88b9645be1 | ||
| 997ebfc28a | |||
| 6a91300d82 | |||
| 889ce9faef | |||
| b3a40efe46 | |||
| 547c7ffdf7 | |||
|
|
82486c7514 | ||
|
|
fe732ea385 | ||
|
|
85ec0faa99 | ||
|
|
9ac1ae9915 | ||
| 895d66b663 | |||
| 0f2a93cd27 | |||
| 323c98edb8 | |||
| aadfac68cd | |||
| 7076cffec5 | |||
| 0ef5a8dead | |||
| 526c8fe750 | |||
| 464eaf613d | |||
| b21d77514a | |||
| 58ed759224 | |||
| 7c742e1016 | |||
| 2e9330b5c5 | |||
| d1b83d069d | |||
| 7d35a85a37 | |||
| 616d9ab46c | |||
| b9c9d6852a | |||
| 29e4a8d4ec | |||
|
|
ceed784acd | ||
|
|
897887f802 | ||
|
|
dcd0c61b8d | ||
| 1285b653ed | |||
| dfd5c65595 | |||
| 315bddea96 | |||
| 938739d535 | |||
| 27f625873d | |||
| f37d25e86b | |||
| b2df70c059 | |||
| 3afd46c59c | |||
| e36c649333 | |||
| b2e7c9fe6e | |||
| 2171d582dc | |||
| b7be459537 | |||
|
|
cbee09c38f | ||
| 6f24e42d1f | |||
| 93bb105dac | |||
| f67b74d57d | |||
| abbf1e4d66 | |||
| e3f6353062 | |||
| 38d5b2bf16 | |||
| 7801c933f0 | |||
| 5cdb9eb9d5 | |||
| dee4670f65 | |||
| 50a6d7190b | |||
| d3371badf8 | |||
| ed1f3e5cdb | |||
| 5947926cd5 | |||
| 61cd77e5df | |||
| d6ec33d6d3 | |||
| 3f002efb53 | |||
| 5191290188 | |||
| cc05543692 | |||
|
|
e94e0c057e | ||
| 350f002ed3 | |||
| 04cf4e7785 | |||
| bf644f4e09 | |||
| 3658aef2e2 | |||
| 190a1171c4 | |||
| 2b20b6bb9d | |||
| caed1aef79 | |||
| 8341956a90 | |||
| de44b48dba | |||
| 7eafd92b2d | |||
| 47dc26fea0 | |||
| a6dd0939b2 | |||
| 898a33a754 | |||
| 1997d9491b | |||
| 6d57ea0368 | |||
| 83c1197dc1 | |||
| 48d145626f | |||
| a57f310c76 | |||
| 1e6cb2e841 | |||
| c676a02ca8 | |||
| ded57cd04a | |||
| b8f9e2f309 | |||
|
|
2fb9168686 | ||
| 9199a64d73 | |||
|
|
a69fb369df | ||
| 0790961bb5 | |||
| c88fa4a003 | |||
| f403dfd7d1 | |||
| 105baf629a | |||
|
|
af37036ac5 | ||
|
|
8717ad6ad0 | ||
|
|
95af0217ff | ||
|
|
fe091fa740 | ||
|
|
00abb4486d | ||
|
|
dadfcb7c16 | ||
|
|
77a9701805 | ||
| bfea4b024a | |||
|
|
3e2cf48223 | ||
| ec2fa16fab | |||
| 8d97a8d140 | |||
| 563aa8b7b4 | |||
| 74475bd191 | |||
| 957a5b7c17 | |||
| 64cc9f78a8 | |||
| e50c411d1e | |||
| 046a7eab7f | |||
| d96732b588 | |||
| a3bfc05068 | |||
| defa2f9431 | |||
| fe4139e268 | |||
| d463bb55d7 | |||
| 5ea19c9475 | |||
| b0cedde0a9 | |||
| e0894c9313 | |||
| 8378d88186 | |||
| ddd363917c | |||
| f310eaf7d9 | |||
| 8972f2d03d | |||
| e88c58916a | |||
| 7d169d8053 | |||
| c018d89ca6 | |||
| 5c402b5400 | |||
| 7ba7edb7d4 | |||
| fe219b5296 | |||
| 1abc041d87 | |||
| c16a0ecd65 | |||
| 6d527a31d7 | |||
| 4d0d631d8a | |||
| 5b2b554a7a | |||
| 33bae657a3 | |||
| 082c575f82 | |||
| 90f553b4f6 | |||
| 6af576c09c | |||
| c380abad5a | |||
| 905099ccdb | |||
| 76b848752c | |||
| 48c3ff584a | |||
| 979affef22 | |||
| 274d3d6858 | |||
| f491acdda9 | |||
| 6ac7ef7807 | |||
| 5d1f5168ad | |||
| 262b9460bd | |||
| f4e4da6dc5 | |||
| 024c99e60d | |||
| aa6b13f3a6 | |||
| 70a79856f2 | |||
| 6a30bb98c6 | |||
| 07cf74d400 | |||
| 94b35545b7 | |||
| 8544c5dc8a | |||
| a546f6de73 | |||
| 7cab0a39e5 | |||
| e19339d808 | |||
| 134975b8ba | |||
| 2d0a57efbd | |||
| 50e94c4d09 | |||
| 15974bdca8 | |||
| 0734e74154 | |||
| f108bc8dc1 | |||
| 4ccda9d6f7 | |||
| db1ba822fe | |||
| bd18a4320a | |||
|
|
b180f11834 | ||
|
|
d4b5c7b9d5 | ||
| a719f6ad98 | |||
|
|
d8894753e0 | ||
| 178061475e | |||
| 3581173193 | |||
| a839631aae | |||
| a3e0d7ffb1 | |||
| 6d4dbcdee7 | |||
| 33ae289ff1 | |||
| fca695ee6b | |||
| 8fd175685d | |||
| 6a3e430a5e | |||
|
|
07efa8b321 | ||
| 9183cb9f37 | |||
| a1bd4836dd | |||
| dc74c0e54b | |||
| 436d19dfea | |||
| 6ca4877f1f | |||
| 6ad302b697 | |||
| b0820095f1 | |||
| b5fda334d4 | |||
| 88beb7b883 | |||
| 49689317b7 | |||
| 052d26c708 | |||
| 76c88848b2 | |||
| 87f069f986 | |||
| ce27773138 | |||
| 0864f83307 | |||
| 624cc67d9b | |||
| 462bac8167 | |||
| 5865eb41f7 | |||
| 2bfd4a942d | |||
| b4a33cba39 | |||
| f02c86e61b | |||
| 5021f50e18 | |||
| 2654521647 | |||
| 778a7eb6bc | |||
| 71d559d6d9 | |||
| b94d2f2fa6 | |||
| 2904f3e2f8 | |||
| 1ad06bcc15 | |||
| bf8c14fc03 | |||
| b46b7aae25 | |||
| 357f51fd5e | |||
| 7f257e045b | |||
| 0dc46d02a4 | |||
| ecf3086aef | |||
|
|
10be875b48 | ||
| 0077d35ff9 | |||
| 2a4a7c975f | |||
| e91f1c3fa4 | |||
| 9d48b4bcdb | |||
| 7f1a35ebe5 | |||
| 5ccd546958 | |||
|
|
def71d8141 | ||
|
|
8feb07ff1b | ||
|
|
1edd76ae8c | ||
| 7613e6e1cb | |||
| 5629a28823 | |||
|
|
b1c2ab90d3 | ||
|
|
268cb0bc18 | ||
|
|
5a78de5a25 | ||
|
|
82972d6e47 | ||
| a201273781 | |||
| 819a8d341f | |||
| b8f7d4fad2 | |||
| dea4e069c5 | |||
| 07c7234bfc | |||
| 223b8bc5ec | |||
|
|
e052a144bd | ||
|
|
318caa886c | ||
|
|
46c61f9ea9 | ||
| ba78e563cd | |||
| acb94db6d6 | |||
|
|
d2cc283bd5 | ||
| 6c66078a65 | |||
|
|
be9c2ff64d | ||
| bb22972eb2 | |||
| e911fb35b6 | |||
| d1490ee771 | |||
| 6d44b4124d | |||
|
|
f8e0d07236 | ||
|
|
e970473876 | ||
|
|
cec05ccbca | ||
|
|
aac59367dc | ||
|
|
0421ca0549 | ||
|
|
43e802fb8e | ||
| 52c4282601 | |||
| 665204bf7a | |||
|
|
f52da56221 | ||
|
|
4d59783809 | ||
|
|
a680d57cac | ||
|
|
f6620be2d9 | ||
|
|
1c2abb543b | ||
|
|
87cf2871a7 | ||
|
|
e041d9041b | ||
| dccf5eae47 | |||
| b2a8c9c45f | |||
| 125a574ff9 | |||
| a75a27ad42 | |||
| 4126f01ef1 | |||
| 9816fc9127 | |||
| b135aa09a3 | |||
| f3a64fd67a | |||
| 864636705d | |||
| 281861cac5 | |||
| c05f50998f | |||
| b2734b179c | |||
| 00a4abf266 | |||
| 47e279b3b3 | |||
| 87f1d635d8 | |||
| fdcb9daadc | |||
| 5fde0501b5 | |||
| f72799f48c | |||
| 60c62b8609 | |||
| e5bc06c138 | |||
| fa5ba0c1ef | |||
| 43be70b27c | |||
| 89400e281e | |||
| 57dc19550d | |||
| a8aa6f192c | |||
| 882ddba324 | |||
| 2f7509b94e | |||
| 10e68aa008 | |||
| a5720e8d7f | |||
| c7aaa98935 | |||
| efcb5710c0 | |||
| 3783fd8506 | |||
| 6f80a9c030 | |||
| a02376497a | |||
| 180c18f6bf | |||
| 2db4c06fe8 | |||
| 122b300c50 | |||
| 0984f7ff5d | |||
| 2d9a5ae7e2 | |||
|
|
02f9660fda | ||
|
|
d3d733ab42 | ||
| bb282da92d | |||
| 38d3b0d047 | |||
| 8ccada67d6 | |||
| 33f7acc9ca | |||
| 5d9563b9d8 | |||
| f55dc0d811 | |||
| 7872983064 | |||
| ea640a8a17 | |||
| 6801d1d1ae | |||
| 3584affbe0 | |||
| 1069fcfc62 | |||
| 59745fb90f | |||
| 1976160ae8 | |||
| 6dd7e49112 | |||
| ecb5352134 | |||
| b96385c4a7 | |||
| 96c1a046d4 | |||
| 00660d3e36 | |||
|
|
b14ca5c625 | ||
| 71fe6137be | |||
| 0a5d565030 | |||
| 6d06e06840 | |||
| edeba897fb | |||
| e4b9c50ee2 | |||
| af7c4e227d | |||
| 96ab887545 | |||
| f8a9da59dd | |||
| 50a6c6d9dd | |||
| 8d181a6683 | |||
| 217516ad59 | |||
| fc4b610d59 | |||
| 382b52e5b2 | |||
| 277738f94d | |||
| cf25229fbc | |||
| 7652d71c94 | |||
| 74e0dcf706 | |||
| 4e1cc6dc80 | |||
| c34f9f9e9f | |||
| ed9066f393 | |||
| 96e1771c25 | |||
|
|
d0d3c7eef5 | ||
| 6875fc0428 | |||
| b88b3a683d | |||
| d97f94075d | |||
| 28f095e56a | |||
| ead87519b1 | |||
| 7cfe3355e4 | |||
| a51ecaaf24 | |||
| 322645da9b | |||
| 5eb63df633 | |||
| e4b5a3ea45 | |||
| 88c1e73720 | |||
| 0393e58d3b | |||
| 6ccfb53329 | |||
| 8a29fbf07d | |||
| 223aee3be2 | |||
| 745d07024c | |||
| 94025c5262 | |||
| ab09eb8a03 | |||
| e826c80ff2 | |||
| 6255fe2d12 | |||
| 1746920699 | |||
| c9b62669de | |||
| d2f367678f | |||
| 56c5fb6c9d | |||
| d8d4c4f55e | |||
| 70423ddb0a | |||
| 94c70485b7 | |||
| 3e558be4d4 | |||
| 95385fa8f4 | |||
| fa4944700c | |||
| df0cf57984 | |||
| 29d1de46e7 | |||
| cb4ab3b436 | |||
| 370e7343d7 | |||
| acd653db70 | |||
| 51ca4aa98e | |||
| d23b59ced2 | |||
| f18ac9db48 | |||
| c20ca3921f | |||
| 96f620455f | |||
| 2dd14dbf04 | |||
| 8346f28497 | |||
| 97967b2b2e | |||
| cf6a257143 | |||
| 275125d230 | |||
| cb7b569d4e | |||
| 89b6f4c5cc | |||
| 8d555eb837 | |||
| 9288528f94 | |||
| e92ffb3894 | |||
| 96bdb42365 | |||
| ecb207d322 | |||
| 64001604cf | |||
| b9d3d22894 | |||
| 25705297cb | |||
| 532637ef7e | |||
| 0a1907ee2c | |||
| d0e76d3d55 | |||
| 1832ea639b | |||
| cd3944b90f | |||
| 9f2f8f7117 | |||
| 1b97b9040d | |||
|
|
8a80a66a80 | ||
|
|
894423e49f | ||
|
|
4ce9013e6a | ||
| 96b95edef8 | |||
| 704854fdf1 | |||
| 6e0393f611 | |||
| e6ceed9ec9 | |||
| cf3d289145 | |||
| 6bd59aad97 | |||
| a5567d491f | |||
| 9604c26973 | |||
| 81ad1ba8c8 | |||
| 1d68122a6f | |||
| 7d9d45ffed | |||
| 50e21b2cc1 | |||
| fb1b1221d3 | |||
| 788c790f9e | |||
| 293d838d80 | |||
| 3da996b8a4 | |||
| 08e3c9cc40 | |||
| 13b4128777 | |||
| b4e79c3f4b | |||
| 2d07f5e924 | |||
|
|
cb29fef17e | ||
| 1d76760dc8 | |||
| b6994034d2 | |||
| 8fc0c072e5 | |||
| e6deb1f281 | |||
|
|
bb0d43018e | ||
|
|
814b734ad3 | ||
| 6f5941472b | |||
| 7699423aa7 | |||
| e48dab9ed3 | |||
| 53a599c6b8 | |||
| 103c0b57f8 | |||
| 7de69c1c10 | |||
| fe7e8ef039 | |||
| 19f4a19dba | |||
| b66da24e39 | |||
| 3dd33274e4 | |||
| c61834e604 | |||
| e082705b0c | |||
| 83ce92d8ac | |||
| 177525817c | |||
| 1dfa0c6b0d | |||
| b3f039d658 | |||
| 7d2e8573f8 | |||
| 5653651c0d | |||
| d3b540199c | |||
| f0430ffeb3 | |||
| d03edf2895 | |||
| 5c1ccfe6fe | |||
| 5b9e90fe7a | |||
| ac32460859 | |||
| e2a8de3acf | |||
| 7bf9f88ee3 | |||
| c20d5c8729 | |||
| e6e80b9841 | |||
| 442c3ed78d | |||
| 2399dccddc | |||
| dd5f37290c | |||
| 5b402478e9 | |||
| e3b7e9f60f | |||
| b0040bd83c | |||
| 56e1268f85 | |||
| 41d9e2f0f5 | |||
| 1fcfb9b22e |
50
.github/workflows/main.yml
vendored
Normal 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
@@ -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
@@ -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
@@ -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
62
CHANGELOG.md
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
/build
|
||||
33
android/android-base/build.gradle
Normal 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
@@ -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
|
||||
2
android/android-base/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<manifest package="org.isoron.androidbase"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"/>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
6
android/android-base/src/main/res/values/base.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
/build
|
||||
24
android/android-pickers/build.gradle
Normal 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'
|
||||
}
|
||||
25
android/android-pickers/proguard-rules.pro
vendored
Normal 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
|
||||
2
android/android-pickers/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<manifest package="com.android"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"/>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
9
android/android-pickers/src/main/res/values/strings.xml
Normal 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
@@ -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
@@ -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
@@ -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
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal 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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
uhabits-android/src/main/play/
|
||||
1
android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':uhabits-android', ':uhabits-core', ':android-base', ':android-pickers'
|
||||
BIN
android/tools/automator-log-converter-1.5.0.jar
Normal file
157
android/tools/coverage-badge/badge.py
Normal 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()
|
||||
21
android/tools/coverage-badge/templates/flat.svg
Normal 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 |
BIN
android/tools/test-butler-app-2.0.2.apk
Normal file
156
android/uhabits-android/build.gradle
Normal 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"
|
||||
}
|
||||
31
android/uhabits-android/proguard-rules.txt
Normal 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
|
||||