Compare commits
1553 Commits
v1.0.0
...
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 | |||
| bf3964a231 | |||
| 657cde75d8 | |||
| c56b86d32c | |||
| 16f20d50a0 | |||
| 7bb88dcb97 | |||
| 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 | |||
| 7bb62c197f | |||
| 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 | |||
| 540a618ba8 | |||
| bf24cc608c | |||
| a73459784e | |||
| de3b97dfdf | |||
| 91996924d9 | |||
| 9fe446b424 | |||
| 3857eaf5e9 | |||
| e29fb58922 | |||
| 404fc869b0 | |||
| 001dd5a7c1 | |||
| 7930cc8f31 | |||
| 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 | |||
| 526830ba61 | |||
| fe1513bb64 | |||
| e06ace9ea8 | |||
| d727dabb2b | |||
| d17e8fcbfb | |||
| f8a9da59dd | |||
| 50a6c6d9dd | |||
| 8d181a6683 | |||
| 217516ad59 | |||
| fc4b610d59 | |||
| 382b52e5b2 | |||
| be3d7145ab | |||
| cf66587644 | |||
| 0dc9ec2e5f | |||
| 0a375ded96 | |||
| fa5d6f8fee | |||
| 277738f94d | |||
| cf25229fbc | |||
| 7652d71c94 | |||
| 74e0dcf706 | |||
| 4e1cc6dc80 | |||
| c34f9f9e9f | |||
| ed9066f393 | |||
| 96e1771c25 | |||
|
|
d0d3c7eef5 | ||
| 6875fc0428 | |||
| b88b3a683d | |||
| d97f94075d | |||
| 28f095e56a | |||
| ead87519b1 | |||
| 7cfe3355e4 | |||
| a51ecaaf24 | |||
| 322645da9b | |||
| 5eb63df633 | |||
| e4b5a3ea45 | |||
| 88c1e73720 | |||
| 0393e58d3b | |||
| 534e6c2d9d | |||
| b6501c9a29 | |||
| 238a1c724d | |||
| 34ca9d17a2 | |||
| 6ccfb53329 | |||
| 8a29fbf07d | |||
| e844390614 | |||
| 223aee3be2 | |||
| 745d07024c | |||
| 94025c5262 | |||
| ab09eb8a03 | |||
| e826c80ff2 | |||
| 6255fe2d12 | |||
| 1746920699 | |||
| c9b62669de | |||
| d2f367678f | |||
| 5e00d07b73 | |||
| 28b6ae7014 | |||
| 2a1bf5fc2e | |||
| ef7483f9dc | |||
| bbb9ed8f99 | |||
| c49d576871 | |||
| bc66ae4f7a | |||
| 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 | |||
| fa416adbb9 | |||
| 8b835b9918 | |||
| 471c5d341f | |||
| d0e76d3d55 | |||
| 1832ea639b | |||
| cd3944b90f | |||
| 9f2f8f7117 | |||
| 1b97b9040d | |||
|
|
8a80a66a80 | ||
|
|
894423e49f | ||
|
|
4ce9013e6a | ||
| 96b95edef8 | |||
| 704854fdf1 | |||
| 6e0393f611 | |||
| e6ceed9ec9 | |||
| cf3d289145 | |||
| 6bd59aad97 | |||
| a5567d491f | |||
| 9604c26973 | |||
| 81ad1ba8c8 | |||
| 1d68122a6f | |||
| 7d9d45ffed | |||
| 50e21b2cc1 | |||
| 57296745b3 | |||
| fb1b1221d3 | |||
| 788c790f9e | |||
| 293d838d80 | |||
| 3da996b8a4 | |||
| 08e3c9cc40 | |||
| 13b4128777 | |||
| b4e79c3f4b | |||
| 2d07f5e924 | |||
|
|
cb29fef17e | ||
| 1d76760dc8 | |||
| b6994034d2 | |||
| 8fc0c072e5 | |||
| e6deb1f281 | |||
|
|
bb0d43018e | ||
| 140ab34a76 | |||
| 0d6ad26505 | |||
| 65cc99dbf7 | |||
| 6855ef9d5e | |||
| 4c58b084c6 | |||
| 5c8e522646 | |||
| 55da0759d4 | |||
|
|
814b734ad3 | ||
| 6f5941472b | |||
| 7699423aa7 | |||
| 692fe3218f | |||
| 387930c08d | |||
| 6bd31f9607 | |||
| 9aafe7160c | |||
| 5cc4aac67a | |||
| 831421bc98 | |||
| e48dab9ed3 | |||
| 161d8f2517 | |||
| bfe4b822b3 | |||
| 53a599c6b8 | |||
| 103c0b57f8 | |||
| 7de69c1c10 | |||
| fe7e8ef039 | |||
| 19f4a19dba | |||
| b66da24e39 | |||
| 3dd33274e4 | |||
| c61834e604 | |||
| e082705b0c | |||
| 83ce92d8ac | |||
| 177525817c | |||
| 1dfa0c6b0d | |||
| b3f039d658 | |||
| 7d2e8573f8 | |||
| 5653651c0d | |||
| d3b540199c | |||
| f0430ffeb3 | |||
| d03edf2895 | |||
| 5c1ccfe6fe | |||
| 5b9e90fe7a | |||
| ac32460859 | |||
| e2a8de3acf | |||
| 7bf9f88ee3 | |||
| 19e79a8559 | |||
| 876d4f0ac7 | |||
| f4f7faf3a4 | |||
| 56f2ae57fe | |||
| 3fe09efe9b | |||
| f0de29fbfe | |||
| 324facfffd | |||
| 03e58f9ef2 | |||
| e6c9f7f0c9 | |||
| 42bdedb86a | |||
| ab0c510fda | |||
| e46fd58664 | |||
| 8532bd402e | |||
| 2c599b18ef | |||
| 0d78ba4ba9 | |||
|
|
611dfa00a5 | ||
|
|
54a195243d | ||
|
|
4fc30fae53 | ||
| b3fe9c65d2 | |||
| 09f1ae8765 | |||
| 0a8b763ece | |||
| edd5f25529 | |||
| d81fdb41dc | |||
| 02c8810e46 | |||
| 6adf8061d3 | |||
| d19d57e5df | |||
| c20d5c8729 | |||
| fd82e6c24b | |||
| 56263efa39 | |||
| d5eacba303 | |||
| 222261c674 | |||
| b1a06df7f8 | |||
| a1fc7dd0d1 | |||
|
|
10131d5124 | ||
| aa94959ad2 | |||
| 967dc2586b | |||
| 908fd1d6ae | |||
| 45fd8a29e1 | |||
| 4624acd477 | |||
| c8cd4fa389 | |||
| 8c4fab28aa | |||
| d6b91cef01 | |||
| e273fe7375 | |||
| ab3b946c65 | |||
| 38cd47c199 | |||
| fa64bd389d | |||
| d03578fd9e | |||
| 79ae181df2 | |||
| 320dca9070 | |||
| f3b9fc825a | |||
| 50da14022f | |||
| 84a02fe541 | |||
| b3aa3d14c0 | |||
| e9cdfd23c4 | |||
| 659ad2d817 | |||
| d537ba0dfa | |||
| 7c14725d88 | |||
| 638b3f763c | |||
| f876fc50bb | |||
| 919504ccfb | |||
| 1a8d9e72a1 | |||
| 8e82f369c7 | |||
| 0a5677211e | |||
| 65071797c9 | |||
| df1751b21a | |||
| 4e952dd87a | |||
|
|
132dce8919 | ||
| 5c8450191a | |||
| b1d00598eb | |||
| 518ade3165 | |||
| dc5d7930a6 | |||
|
|
ec34043041 | ||
| 4d5407a5cc | |||
| 904489d812 | |||
| d2436a3165 | |||
|
|
bc19858bca | ||
| a998a62cdb | |||
| 1a89bb02be | |||
| 6e8ea471aa | |||
| ec42fb54f4 | |||
| c46fa84135 | |||
| 16dcc0cbc2 | |||
| 6f10039aba | |||
|
|
5cbc0a3292 | ||
| 7f67a9eb63 | |||
| d3f7ebd60c | |||
| 461fe1f0b6 | |||
| 8eb9f398d5 | |||
| f178bcbdd2 | |||
| 66c3136fad | |||
| 4b04966617 | |||
| 3ea21fe823 | |||
| e0df69beb6 | |||
| b5cd4584b2 | |||
| af7c8b1f2e | |||
|
|
283cb2a3f0 | ||
| 79ddbca307 | |||
| 46a0777195 | |||
| b35305e16c | |||
| 8c4a745ecb | |||
|
|
e5162c48ab | ||
| b54c4de5f7 | |||
| 2bdc0b4f5e | |||
| 22db61db01 | |||
| c7cfbd1643 | |||
| 8655437f3e | |||
| 3ad3cf54ec | |||
| 9a470cc61d | |||
| fc2087fe68 | |||
| 7b8ab6a625 | |||
| 2d40fb0b82 | |||
| c961045b63 | |||
| d0db3359fe | |||
| 11378e07bf | |||
| 8132188e46 | |||
| 7eb454788f | |||
| c262adbe85 | |||
| 05aa5b1172 | |||
| dd3d78b82c | |||
| 3d4ae2126b | |||
| 5aa9114aff | |||
| 77e0ad007f | |||
| 2e53b75705 | |||
|
|
0ec03035f5 | ||
|
|
67ae48b527 | ||
|
|
82d2931559 | ||
| a527140802 | |||
| 9ec3d9048a | |||
| 3ed4f3b280 | |||
| f101975320 | |||
| 3a7f27755c | |||
| 3a3be664f7 | |||
| 33ae8d1edf | |||
| cb3b8f2cde | |||
| 307e025b1a | |||
| 03dd24d17b | |||
| 5f4ac21a41 | |||
| b33616d363 | |||
| b7d1c6d254 | |||
| bd30ceee1b | |||
| fe96313162 | |||
| 938fe3325e | |||
| 237de035bb | |||
| ddc85ced0b | |||
| eceb1bfb7d | |||
| 3b737996e9 | |||
| 7f75f9b6da | |||
| 74f78f0fdf | |||
| ef63dd19e7 | |||
| c7878d979f | |||
| ebd294be63 | |||
| 748cec06a8 | |||
| 15a4a2c002 | |||
| 37a9e793e7 | |||
| d54de9df89 | |||
| 94a5db2208 | |||
| a984467516 | |||
| d6dacfd24b | |||
| 3938ae6fa8 | |||
| 7dbbc51a9a | |||
| c9d1bb821c | |||
| 77f406dcee | |||
| 61b0b1fdea | |||
| 8cde0d6aca | |||
| fa9f90a09e | |||
| 28eb615b0e | |||
| c1e10e09a5 | |||
| 35e93fddc6 | |||
| 33596a2797 | |||
| 207f026ceb | |||
| 0f12d02990 | |||
| bc4bbaefac | |||
| a802053ef7 | |||
| cf98d1a5c3 | |||
| cc1e56894b | |||
| 06b5f89b7a | |||
| 17423b3ecd | |||
| 1526f617c5 | |||
| 365eb400d0 | |||
| 785d57c778 | |||
| 8e9f1aa166 | |||
| b2f97cb0a5 | |||
|
|
4c1ff4f0a8 | ||
| 94a48133ec | |||
| 71dd6cde89 | |||
| b33420cabb | |||
| dbe268b8e9 | |||
| 3d53812d7f | |||
| 5b4b436f0f | |||
| 6ef2983906 | |||
| b749f7f391 | |||
| 922b234307 | |||
| 31fdae1c8b | |||
| 5d61fdd3d0 | |||
| 588b45d47b | |||
| 9a059275ce | |||
| 4ae813e6f9 | |||
| 52701666bc | |||
| a90e26691f | |||
| f0c62a5908 | |||
| e899a70eb0 | |||
| aa41717c66 | |||
| 6ba6d7c8c1 | |||
| efd0d1e051 | |||
| a11ad6e909 | |||
| a060cbe578 | |||
| a445ca962b | |||
| 2b6fc06b86 | |||
| 98ad3aab9d | |||
| c7e63a40da | |||
| b6ed33b1e6 | |||
| 91e39372a1 | |||
| c2dd26eeb3 | |||
| 5831340343 | |||
| d7f6f52a49 | |||
| 93b442332d | |||
| e248824bcd | |||
| ec4a381d70 | |||
| b13f2b4228 | |||
| efc7b2cebb | |||
| add08d6054 | |||
| 5d8a348aaf | |||
| ec0e8ac24c | |||
| fee3137a6f | |||
| abe6b10964 | |||
| 6484b96e5a | |||
| 3d3d5b9b96 | |||
| 14364901ff | |||
| 440706882b | |||
| ca9d56e59e | |||
| 9a6dafaa79 | |||
| 9a44774284 | |||
| 2b23b36e36 | |||
| 78d4f86cab | |||
| 18e8390aed | |||
| cabcd5b1bf | |||
| 7e8a2a0c1c | |||
| 650971bf36 | |||
| 00774368d4 | |||
| cbf1bd3e19 | |||
| 4061921b93 | |||
| 59d42fe62f | |||
| 3ffa079e24 | |||
| bb950d61fc | |||
| 0ad0e5cf36 | |||
| 3b56d6d596 | |||
| 8d57273987 | |||
| b98853ab26 | |||
| 071cad73d4 | |||
| 16dc9c25d2 | |||
| 6b23858136 | |||
| e0198e9926 | |||
| 6445bf62bc | |||
| 83ef8564e1 | |||
| 88e8aad0d8 | |||
| a4b6728721 | |||
| e6e80b9841 | |||
| 442c3ed78d | |||
| ca9745f550 | |||
| 33bc8f78e7 | |||
| 0f223f8504 | |||
| 007996c69e | |||
| ff9d50b32a | |||
| 38024d71ce | |||
| 9ec1afc208 | |||
| e0888a9b4d | |||
| 8aade2f145 | |||
| 1ba1d775f7 | |||
| 605593d739 | |||
| bfbfcbd8cf | |||
| bd6cba8303 | |||
| befc3a0ad8 | |||
| f4c963e2c1 | |||
| e9a4a047c1 | |||
| 7ce1988d2e | |||
| 53911fa410 | |||
| f5f43d9a16 | |||
| 2399dccddc | |||
| dd5f37290c | |||
| dbf569ff87 | |||
| d0e475ad78 | |||
| b3199cf092 | |||
| 73e6f2a2d4 | |||
| 5b402478e9 | |||
| e3b7e9f60f | |||
| b0040bd83c | |||
| 56e1268f85 | |||
| 41d9e2f0f5 | |||
| 1fcfb9b22e | |||
| 2c80544aaa | |||
| 097a5be864 | |||
| 0287473e61 | |||
| f403dc6f77 | |||
| 23466523df | |||
| 83ca136346 | |||
| 7830b599db | |||
| 49376d4f41 | |||
| 8ee9c9c0b1 | |||
| ee9da29422 | |||
| ae7b0408b2 | |||
| d926d9dbd6 | |||
| 5694da2413 | |||
| 7e0ae144b8 | |||
| 1426d887e7 | |||
| acb8e820fd | |||
| eae0d66f51 | |||
| e933cbbc43 | |||
| 0b95b6a78c | |||
| 5e873a3659 | |||
| d292ecd988 | |||
| 19b484c368 | |||
| 834ae92d87 | |||
| 26ce92d381 | |||
| 0eb12812d4 | |||
| a1a636c718 | |||
| b2811b9797 | |||
| 4db7a6e89c | |||
| 677a643e5b | |||
| dbca3238f6 | |||
| ae5dd700f3 | |||
| 7e4c508d7d | |||
| d0c056eeaa | |||
| f172c69eed | |||
| 0cd4a41438 | |||
| d77c78249c | |||
| d9d48ff984 | |||
| 7be71ba4f8 | |||
| f0534fefbc | |||
| 30ef75bb45 | |||
| 44ed74a693 | |||
| 4aea527e07 | |||
| 638539259a | |||
| 75dec88411 | |||
| 2dc4fcbc46 | |||
| 7977d5247c | |||
| 8b18e32a16 | |||
| dbcad9a5f4 | |||
| 3bba75ff50 | |||
| 9bbeee66e2 | |||
| c6e76f4cfd | |||
| 980db1d171 | |||
| aee5d975db | |||
| e2bb4371d3 | |||
| fcee8552f0 | |||
| a4864e4612 | |||
| 8ddf4574e3 | |||
| f1ca8c5449 | |||
| 81a188f125 | |||
| 44402bf3a4 | |||
| 7f159149ef | |||
| 0c1e8d5131 | |||
| a4e3f7e037 | |||
| b354a0765b | |||
| bbd959dfda | |||
| d05d404c55 | |||
| 8938b0c9a6 | |||
| bd6fcd066c | |||
| 6a5f2abb76 | |||
| 33b5215b00 | |||
| 5d808dd2e8 | |||
| 767ec1b6de | |||
| 9e5f3d8f58 | |||
| 775d028629 | |||
| 4bfb839370 | |||
| 8f64b696d8 | |||
| c4bf31e778 | |||
| c757ce6548 | |||
| 78994bb3c4 | |||
| 2373ba5407 | |||
| b601a643dd | |||
| 90e513e778 | |||
| 728c9557f0 | |||
| 5bd0c842f5 | |||
| 97711087f9 | |||
| 2fe2d8834a | |||
| 93a893d660 | |||
| feff9a18e6 | |||
| b42565b770 | |||
| 3de702ced2 | |||
| 42f7f4042d | |||
| 2115a590f2 | |||
| f5f3ec9f88 | |||
| 6d5a8f5753 | |||
| 06fc04092b | |||
|
|
24c02605d1 | ||
| 11fcb67624 | |||
| abc92e390a | |||
| 52c07660b1 | |||
| 35f778c376 | |||
| 436ae7e574 | |||
| 5c683a77c2 | |||
| dd1f6c9efc | |||
| cba089407b | |||
| a7b0395a2a | |||
| 7d2946360f | |||
| 04e8432522 | |||
| 13d34945b4 | |||
| 756578508e | |||
| 38fc7650b9 | |||
| cf06ec0a4b | |||
| f0701f7b35 | |||
| de8018af7d | |||
| ac885e1503 | |||
| f85e09288c | |||
| 6f11596bb7 | |||
| 64c4367706 | |||
| c08702341a | |||
| abb8c09bc9 | |||
| 87255ceb25 | |||
| aedbfded0f | |||
| 779c0040bd | |||
| 7bba6a887f | |||
| 52a1d19793 | |||
| 35e346e7ea | |||
| 3102158bdb | |||
| 7afa65a53c | |||
| 2e5533d480 | |||
| b29c7e695a | |||
| 7df745eb82 | |||
| c0d518ca4d | |||
| eeff14a2ca | |||
| af3b18f518 | |||
| 3811f4b339 | |||
| 50a8aece6f | |||
| 2f66cfc417 | |||
| 132ce36a57 | |||
| 147f010d1b | |||
| afc91ed89a | |||
| 684c37d167 | |||
| 8b296ce2ae | |||
| e2e9e2e27a | |||
| 8b2f31c44a | |||
| 83db9fe2f2 | |||
| 9891139b5a | |||
| cbc8cbfea3 | |||
| 96c46b655d | |||
| 0de52d4fa3 | |||
| 16d85dc4c7 | |||
| c51b1fd399 | |||
| eb8dd1d450 | |||
| 357646152f | |||
| ae5be202b2 | |||
| cc3e2153d0 | |||
| 7433a2413d | |||
| 1cd8eb6849 | |||
| 7f009e2365 | |||
| 4cec7d7367 | |||
| f59c0bd7ff | |||
| 2d2bb8b4ed | |||
| e476096ae1 | |||
| e2c814a982 | |||
| 4fcaa68baf | |||
| 02e45dbe27 | |||
| bf6562f854 | |||
| 3c927e009a | |||
| a9bcae0f2f | |||
| bc54f3f916 | |||
| 5763d76167 | |||
| 86b66b1388 | |||
| 5e0422848b | |||
| a67a3297b1 | |||
| b2f2d4ad28 | |||
| 4dc8201dcb | |||
| f24e300b40 | |||
| 21157eaa19 | |||
| 0ea2c8e5c8 | |||
| 826491bc90 | |||
| 72f5ca5531 | |||
| f9da90a93a | |||
| 96c6a97ad0 | |||
| 20469789ea | |||
| abafffcff1 | |||
| d45a4445cc | |||
| 94f56a8869 | |||
| bc331d0a4d | |||
| 2ba7246e5f | |||
| 67b88c5012 | |||
| e0b637e84d | |||
| e14d73cf32 | |||
| 16b9584868 | |||
| c091116105 | |||
| 37e1a6233c | |||
| 6838a0f2d3 | |||
| 53e737c169 | |||
| 285f79ed95 | |||
| 4f65c8bef1 | |||
| 51b7cfc8fa | |||
| 55ed2585ea | |||
| 2c62e1578a | |||
| e3d96c0431 | |||
| b4c2d2237a | |||
| d14fbbd130 | |||
| e2c99d745e | |||
| dfe41176bc | |||
| 1ef4f8d456 | |||
| 6c810ee7a3 | |||
| 5115379fdd | |||
| d176ea91fb | |||
| 62df660079 | |||
| 790beb185a | |||
| 59c0af17d7 | |||
| 28dad560a6 | |||
| 9e410f8395 | |||
| 3656c51e95 | |||
| d3ebb4ff25 | |||
| 3c595bc79d | |||
| 1120f56dd4 | |||
| 2dfbcfcdb0 | |||
| 90a3964f0f | |||
| 6c05366f0f | |||
| dcc771abe7 | |||
| 743431ef67 | |||
| d5fc1a6886 | |||
| eeb0b109ae | |||
| ad391fa791 | |||
| e8bbae8ef9 | |||
| c9793df7c7 | |||
| 3c6114fc87 | |||
| 4ea4201b6a | |||
|
|
30b5b90091 | ||
| e6b7b8b590 | |||
| 2d675ed9b0 | |||
| 1db2f69f05 | |||
| 49a80faca3 | |||
| aa5df56437 | |||
| 581197be03 | |||
| dfe5c4954e | |||
| a0582b65d5 | |||
| 9edc3e12c9 | |||
| 7bb3db213f | |||
| e05a69b527 | |||
| d5df6ddcdb | |||
| 3016263750 | |||
| f9377e1768 | |||
| ae0dad9120 | |||
| be114bde7f | |||
| e7148abc2e | |||
| 2d87076a48 | |||
| 7da4ddf91b | |||
| e4e8d77acc | |||
| 7945a5faf9 | |||
| 3f7d25461d | |||
| 519737a1c3 | |||
| c3ff1fbe03 | |||
| d39e1978a2 | |||
| 536c095f23 | |||
| 8aaa5aca28 | |||
| 5540a66e08 | |||
| 01ffc6c144 | |||
| 21aa658acc | |||
| e17667d85d | |||
| f2948dac6f | |||
| 31a7d92f77 | |||
| 82d7970f86 | |||
| af1a21a959 | |||
| e7c1575083 | |||
| d5a14ca55b | |||
| c4d6b80944 | |||
| c4b6cad6bb | |||
| 7070530d0e | |||
| 1332a70eb4 | |||
| 11c9a3dab0 | |||
| 74f7b0fd28 | |||
| e693504183 | |||
| 7d9a94ae9e | |||
| 326cb8f73f | |||
| 6826bd1ddc | |||
| b5bc347624 | |||
| 55c058ff42 | |||
| e829bb8109 | |||
| 0921f9346e | |||
| eb017bf99b | |||
| 79b6ef8200 | |||
| 7ba62d6784 | |||
| f5e4a88415 | |||
| 075b7812eb | |||
| 3d42505fb9 | |||
| 824f98dd96 | |||
| fcb82bcb72 | |||
| 851cae3662 | |||
| 7778c5fb21 | |||
| ef847dac17 | |||
| 8102c18c67 | |||
| 59ed9ec9bd | |||
| ffdc923268 | |||
| 9232378d04 | |||
| b20fd44cbc | |||
| ded8800017 | |||
| 606f66b8d9 | |||
| 1bb6cd405b | |||
| babf7d64f0 | |||
| dfbcf78dd7 | |||
| e01c668e4d | |||
| 2eef696027 | |||
| ddd10cacd1 | |||
| 0cdde4901e | |||
| f13e9b7362 | |||
| 558f72d7c5 | |||
| 6e493f55bc | |||
| 5186ab840a | |||
| 65fd82d888 | |||
| 866b62987c | |||
| 18abb2038f | |||
| f7f4b5eeb0 | |||
| 45a7433773 | |||
| 40420c3a77 | |||
| 988b39f2e5 | |||
| a0803966f9 | |||
| e4af662836 | |||
| e95adab422 | |||
| 012fed01eb | |||
| 4cf2b8072b | |||
| 196a79a88c | |||
| 4b7e2e79a7 | |||
| 9156bba267 | |||
| 1a18bb939d | |||
| 144524e53b | |||
| 3d1c53396c | |||
| 1930db3cd1 | |||
| a2c2a5531a | |||
| eee2605f74 | |||
|
|
9df0c9ae9e | ||
| e0533505c1 | |||
|
|
433894336c | ||
| 9061182301 | |||
| 8c45f52d56 | |||
| 53c9dca3e2 | |||
| 8b37daa9fb | |||
| f9c2e83c79 | |||
| 71bdc70c1a | |||
| 5e4a40579a | |||
| d326be1224 | |||
| fb8a09c95c | |||
| 1635b9905d | |||
| 2d88fc0b20 | |||
| 1d74359c06 | |||
| f75f77cec7 | |||
| 8b10138cd6 | |||
| 2b40633110 | |||
| 1102d05a61 | |||
| 51e8c2f111 | |||
| 547e4e5f63 | |||
| 84d5c2aac6 | |||
| 2b3b423fa3 | |||
| 3b28c37c5e | |||
| e749e787ad | |||
| 9c5d582f24 | |||
| 75a4edb61c | |||
| 34c0758308 | |||
| 85963ae061 | |||
| e3390d5397 | |||
| 59a2f31a73 | |||
| c01f8450d3 | |||
| cea5241135 | |||
| 7784fc5c75 | |||
| 6dd017f33e | |||
| c8cd9f85f6 | |||
| d038bdb741 | |||
| f55e8d2c85 | |||
| f8dc1d9eae | |||
| 85393b0d40 | |||
| 75599ad20c | |||
| c6b948cbf5 | |||
| 4d42133a4b | |||
| 5b151805ff | |||
| aa86826bdb | |||
| 821373a340 | |||
| 8f37e293b1 | |||
| 0fb8ed0b53 | |||
| 0c696b2eb2 | |||
| cd127bc3f8 | |||
| 2cfc809490 | |||
| ba31dee16a | |||
| 146c743fb8 | |||
| 0c00e9ec2d | |||
| 49af55a2de | |||
| 0114b48197 | |||
| 09f615a5e6 | |||
| 9014acc548 | |||
| 4fb386be86 | |||
| ea8606be97 | |||
| e0527dc8ff | |||
| aaf2789a21 | |||
| f8dc64cc6b | |||
| ced5b751be | |||
| 8a60dda74e | |||
| c8c4df6ef7 | |||
| 0c0ac9dee5 | |||
| fdf6c91929 | |||
| 08d6e39a17 | |||
| b9bc7bd1b5 | |||
| 7b73238448 | |||
| 83ccfb82ac | |||
| 199d35d9c7 | |||
| 382a2fe600 | |||
| e02f9c1d60 | |||
| 5e7636d7ff | |||
| 616322cd35 | |||
| 299c6a0c1d | |||
| b4911b6cb4 | |||
| f41f877107 | |||
| 58aa7f6687 | |||
| d196e01da0 | |||
| 1fbd12a947 | |||
| 7493291ade | |||
| 2a750704d9 | |||
| eb79113e4c | |||
| cb2f3823cd | |||
| 39e29dabb8 | |||
| 51d1b93d03 | |||
| 8acbc63914 | |||
| ac8e78ff24 | |||
| 162ded66d8 | |||
| 5428209543 | |||
| 141fd30d70 | |||
| 48d446a243 | |||
| ae7869d3a2 | |||
| b8cacaffa9 | |||
| 4def8f0409 | |||
| f0d12e9925 | |||
| a2331260e4 | |||
| c1a846d42b | |||
| 031d684b3e | |||
| 3a770e71e3 | |||
| b29dd8ea79 | |||
| 7f1553a4a1 | |||
| d748f5d6de | |||
| 7234e072e6 | |||
| 7f71f46367 | |||
| c1dae021bf | |||
| 88455acc76 | |||
| 6a1cb09ca2 | |||
| 33d7ab52ca | |||
| d2682358c2 | |||
| f511ca2028 | |||
| d5774e8511 | |||
| b6e7e72f5a | |||
| 27220c9ab2 | |||
| f1424e5820 | |||
| a18e0fbda0 | |||
| 4c3a72df81 | |||
| e9ce50f686 | |||
| acb26964f3 | |||
| 917d1218ae | |||
| f5ccd7d8c3 | |||
| 456a9e49a9 | |||
| 6b05004647 | |||
| f9a9339042 | |||
| 5e21d877c5 | |||
| af0ef90e4d | |||
| 02b3ea58cf | |||
| 0dcedb3c59 | |||
| 637d75fd2e | |||
| 0cd8dd973b | |||
| 2898a21157 | |||
| 0fa25c6701 | |||
| 843f3b06a1 | |||
| a9b8b7e1e2 | |||
| 966e7ccd8a | |||
| d58be08fee | |||
| eb057b51d3 | |||
| 8c88e7fd5b | |||
| 756d3aa48f | |||
| 1a4dbd9cba | |||
| c68176ad09 | |||
| b0ccf3464f | |||
| dcaff3d1b8 | |||
| 693e0143b5 | |||
| cdc80bdbbd | |||
| 948bca1150 | |||
| 68c4b26031 | |||
| 56bed8206e | |||
| 8e2f06c211 | |||
| 08f2fe84d7 | |||
| 0e5764cf5d | |||
| e6a5751959 | |||
| eefc738ee2 | |||
| 322650345c | |||
| 5c77a44611 | |||
| 28900d0981 | |||
| 77281e11f5 | |||
| e06b0e79cc | |||
| d6f31b8775 | |||
| d862c85874 | |||
| 5bd1b70cd9 | |||
| 191b9b9c1f | |||
| c36cdb1e42 | |||
| 4c53bd3763 | |||
| 7984670a3c | |||
| a58a95f125 | |||
| 378b4cb84b | |||
| ec32f3b681 | |||
| ef5f1a8f8c | |||
| 46f152d5d2 | |||
| f3a096b660 | |||
| c8de4e13f9 | |||
| 1ee3fc79f0 |
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
|
||||
54
.gitignore
vendored
@@ -1,33 +1,23 @@
|
||||
#built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# files for the dex VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Windows thumbnail db
|
||||
Thumbs.db
|
||||
|
||||
# OSX files
|
||||
.DS_Store
|
||||
|
||||
# Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# Android Studio
|
||||
.idea
|
||||
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
|
||||
.gradle
|
||||
build/
|
||||
*.iml
|
||||
*.pbxuser
|
||||
*.perspective
|
||||
*.perspectivev3
|
||||
*.swp
|
||||
*~.nib
|
||||
*.hprof
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
.externalNativeBuild
|
||||
.gradle
|
||||
.idea
|
||||
.secret
|
||||
build
|
||||
build/
|
||||
captures
|
||||
local.properties
|
||||
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
235
CHANGELOG.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# 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
|
||||
|
||||
### 1.7.6 (July 18, 2017)
|
||||
|
||||
* Fix bug that caused widgets not to render sometimes
|
||||
* Fix other minor bugs
|
||||
* Update translations
|
||||
|
||||
### 1.7.3 (May 30, 2017)
|
||||
|
||||
* Improve performance of 'sort by score'
|
||||
* Other minor bug fixes
|
||||
|
||||
### 1.7.2 (May 27, 2017)
|
||||
|
||||
* Fix crash at startup
|
||||
|
||||
### 1.7.1 (May 21, 2017)
|
||||
|
||||
* Fix crash (BadParcelableException)
|
||||
* Fix layout for RTL languages such as Arabic
|
||||
* Automatically detect and reject invalid database files
|
||||
* Add Hebrew translation
|
||||
|
||||
### 1.7.0 (Mar 31, 2017)
|
||||
|
||||
* Sort habits automatically
|
||||
* Allow swiping the header to see previous days
|
||||
* Import backups directly from Google Drive or Dropbox
|
||||
* Refresh data automatically at midnight
|
||||
* Other minor bug fixes and enhancements
|
||||
|
||||
### 1.6.2 (Oct 13, 2016)
|
||||
|
||||
* Fix crash on Android 4.1
|
||||
|
||||
### 1.6.1 (Oct 10, 2016)
|
||||
|
||||
* Fix a crash at startup when database is corrupted
|
||||
|
||||
### 1.6.0 (Oct 10, 2016)
|
||||
|
||||
* Add option to make notifications sticky
|
||||
* Add option to hide completed habits
|
||||
* Display total number of repetitions for each habit
|
||||
* Pebble integration: check/snooze habits from the watch
|
||||
* Tasker/Locale integration: allow third-party apps to add checkmarks
|
||||
* Export an unified CSV file, with checkmarks for all the habits
|
||||
* Increase width of name column according to screen size
|
||||
* Stop showing reminders for archived habits
|
||||
* Add Danish, Dutch, Greek, Hindi and Portuguese (PT) translations
|
||||
* Other minor fixes and enhancements
|
||||
|
||||
### 1.5.6 (Jun 19, 2016)
|
||||
|
||||
* Fix bug that prevented checkmark widget from working
|
||||
|
||||
### 1.5.5 (Jun 19, 2016)
|
||||
|
||||
* Fix bug that prevented check button on notification to work sometimes
|
||||
* Fix bug that caused back button to apparently erase some checkmarks
|
||||
* Complete French translation
|
||||
* Add Croatian and Slovenian translations
|
||||
|
||||
### 1.5.4 (May 29, 2016)
|
||||
|
||||
* Fix crash upon opening settings screen in some phones
|
||||
* Fix missing folders in CSV archive
|
||||
* Add Serbian translation
|
||||
|
||||
### 1.5.3 (May 22, 2016)
|
||||
|
||||
* Complete Arabic and Czech translations
|
||||
* Fix crash at startup
|
||||
* Fix checkmark widget on custom launchers
|
||||
|
||||
### 1.5.2 (May 19, 2016)
|
||||
|
||||
* Fix missing attachment on bug reports
|
||||
* Fix bug that prevents some widgets from rendering
|
||||
* Complete Japanese translation
|
||||
|
||||
### 1.5.1 (May 17, 2016)
|
||||
|
||||
* Fix build on F-Droid
|
||||
|
||||
### 1.5.0 (May 15, 2016)
|
||||
|
||||
* Add night mode, with AMOLED support
|
||||
* Backport material design to older devices
|
||||
* Display more information on statistics screen
|
||||
* Display score on main screen and checkmark widget
|
||||
* Make widgets react immediately to touch
|
||||
* Reschedule reminders after reboot
|
||||
* Pick first day of the week according to country
|
||||
* Add option to reverse order of days on main screen
|
||||
* Add option to change notification sounds
|
||||
* Add Catalan, Indonesian, Turkish, Ukrainian translations
|
||||
* Switch between Simplified/Traditional Chinese according to country
|
||||
|
||||
### 1.4.1 (April 9, 2016)
|
||||
|
||||
* Show error message on widgets, instead of crashing
|
||||
* Complete French translation
|
||||
* Minor fixes to other translations
|
||||
|
||||
### 1.4.0 (April 7, 2016)
|
||||
|
||||
* Ability to import data from third-party apps
|
||||
* Ability to save and restore full database backup
|
||||
* Show more information on streak chart
|
||||
* Simplify interface for creating habits
|
||||
* Add link to Frequently Asked Questions (FAQ)
|
||||
* Reduce app loading time and lag on widgets
|
||||
* Generate bug reports on crash and from settings screen
|
||||
* Disable vibration according to phone settings
|
||||
* Add Czech translation
|
||||
* Fix wrong month names for some languages
|
||||
|
||||
### 1.3.3 (March 20, 2016)
|
||||
|
||||
* Add Spanish and Korean translations
|
||||
* Make small corrections to other translations
|
||||
* Fix incorrect date in history calendar
|
||||
|
||||
### 1.3.2 (March 18, 2016)
|
||||
|
||||
* Add Arabic, Italian, Polish, Russian and Swedish translations
|
||||
* Minor fixes to German and French translations
|
||||
* Minor bug fixes
|
||||
|
||||
### 1.3.1 (March 15, 2016)
|
||||
|
||||
* Fixes crash on devices with large screen, such as the Nexus 10
|
||||
* Fixes crash when clicking widgets and reminders of deleted habits
|
||||
* Other minor bug fixes
|
||||
|
||||
### 1.3.0 (March 12, 2016)
|
||||
|
||||
* New frequency plot: view total repetitions per day of week
|
||||
* New history editor: put checkmarks in the past
|
||||
* Add German, French and Japanese translations
|
||||
* Add about screen, with credits to all contributors
|
||||
* Fix small bug that prevented habit from being reordered
|
||||
* Fix small bug caused by rotating the device
|
||||
|
||||
### 1.2.0 (March 4, 2016)
|
||||
|
||||
* Ability to export habit data as CSV
|
||||
* Widgets (checkmark, history, score and streaks)
|
||||
* More natural scrolling on data views (fling)
|
||||
* Minor UI improvements on pre-Lollipop devices
|
||||
* Fix crash on Samsung Galaxy TabS 8.4
|
||||
* Other minor bug fixes
|
||||
|
||||
### 1.1.1 (February 24, 2016)
|
||||
|
||||
* Show reminder only on chosen days of the week
|
||||
* Rearrange habits by long-pressing then dragging
|
||||
* Select and modify multiple habits simultaneously
|
||||
* 12/24 hour format according to phone preferences
|
||||
* Permanently delete habits
|
||||
* Usage hints during startup
|
||||
* Translation to Brazilian Portuguese and Chinese
|
||||
* Other minor fixes
|
||||
|
||||
### 1.0.0 (February 19, 2016)
|
||||
|
||||
* Initial release
|
||||
207
NOTICE.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Copyright Notices
|
||||
|
||||
### ActiveAndroid
|
||||
|
||||
<https://github.com/pardom/ActiveAndroid>
|
||||
|
||||
Copyright (C) 2010 Michael Pardo
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Android Open Source Project
|
||||
|
||||
<https://source.android.com/>
|
||||
|
||||
Copyright (C) 2013 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### FontAwesome
|
||||
|
||||
<http://fontawesome.io>
|
||||
|
||||
Font Awesome is a full suite of 605 pictographic icons for easy scalable
|
||||
vector graphics on websites, created and maintained by Dave Gandy. Licensed
|
||||
under the SIL OFL 1.1.
|
||||
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
### Material Design Icons
|
||||
|
||||
<https://github.com/google/material-design-icons>
|
||||
|
||||
Material design icons are the official icon set from Google that are designed
|
||||
under the material design guidelines. Available under the Creative Common
|
||||
Attribution 4.0 International License (CC-BY 4.0).
|
||||
|
||||
### Android Flow Layout
|
||||
|
||||
<https://github.com/ApmeM/android-flowlayout>
|
||||
|
||||
Extended linear layout that wrap its content when there is no place in the current line.
|
||||
|
||||
Copyright 2011, Artem Votincev (apmem.org)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
### Dagger 2
|
||||
|
||||
<https://github.com/google/dagger>
|
||||
|
||||
A fast dependency injector for Android and Java.
|
||||
|
||||
Copyright 2012 Square, Inc.
|
||||
Copyright 2012 Google, Inc.
|
||||
|
||||
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.
|
||||
|
||||
### AutoFactory
|
||||
|
||||
<https://github.com/google/auto/tree/master/factory>
|
||||
|
||||
A source code generator for JSR-330-compatible factories.
|
||||
|
||||
Copyright 2013 Google, Inc.
|
||||
|
||||
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.
|
||||
|
||||
### Retrolambda
|
||||
|
||||
<https://github.com/orfjackal/retrolambda>
|
||||
|
||||
Backport of Java 8's lambda expressions to Java 7, 6 and 5
|
||||
|
||||
Copyright (c) 2013-2016 Esko Luontola and other Retrolambda contributors
|
||||
This software is released under the Apache License 2.0.
|
||||
The license text is at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
### PebbleKit SDK
|
||||
|
||||
<https://github.com/pebble/pebble-android-sdk/>
|
||||
|
||||
Android PebbleKit SDK to talk to the Pebble via Bluetooth
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2014 - 2015 Pebble Technology
|
||||
|
||||
### AppIntro
|
||||
|
||||
<https://github.com/PaoloRotolo/AppIntro>
|
||||
|
||||
Make a cool intro for your Android app.
|
||||
|
||||
Copyright 2015 Paolo Rotolo
|
||||
Copyright 2016 Maximilian Narr
|
||||
|
||||
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.
|
||||
|
||||
### ButterKnife
|
||||
|
||||
<https://github.com/JakeWharton/butterknife>
|
||||
|
||||
Bind Android views and callbacks to fields and methods
|
||||
|
||||
Copyright 2013 Jake Wharton
|
||||
|
||||
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.
|
||||
|
||||
### opencsv
|
||||
|
||||
<http://opencsv.sourceforge.net/>
|
||||
|
||||
Opencsv is a very simple csv (comma-separated values) parser library for Java.
|
||||
|
||||
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.
|
||||
134
README.md
@@ -1,17 +1,21 @@
|
||||
# Habits Tracker
|
||||
<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>
|
||||
|
||||
Habits Tracker is a simple Android app that helps you create and maintain good habits. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source, with no intrusive permissions. Join the open beta at [Google Play Store](https://play.google.com/apps/testing/org.isoron.uhabits).
|
||||
# Loop Habit Tracker
|
||||
|
||||
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.
|
||||
|
||||
## Features
|
||||
|
||||
* Simple and beautiful interface, following the Material Design guidelines.
|
||||
* Advanced algorithms for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress.
|
||||
* Detailed graphs and statistics, showing how did you habits improve over time. Scroll back to see the complete history of your habit.
|
||||
* Support for both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day.
|
||||
* Habit reminders at a chosen hour of the day.
|
||||
* Support for Android Wear. Reminders can be checked or dismissed from the watch.
|
||||
* Completely ad-free and open source. There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under the GPLv3.
|
||||
<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="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
</p>
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -19,12 +23,104 @@ Habits Tracker is a simple Android app that helps you create and maintain good h
|
||||
[![Edit habit][screen2th]][screen2]
|
||||
[![Habit strength][screen3th]][screen3]
|
||||
[![Habit history and streaks][screen4th]][screen4]
|
||||
[![Widgets][screen5th]][screen5]
|
||||
[![Night mode][screen6th]][screen6]
|
||||
|
||||
[screen1]: screenshots/original/uhabits1.png
|
||||
[screen2]: screenshots/original/uhabits2.png
|
||||
[screen3]: screenshots/original/uhabits3.png
|
||||
[screen4]: screenshots/original/uhabits4.png
|
||||
[screen1th]: screenshots/thumbs/uhabits1.png
|
||||
[screen2th]: screenshots/thumbs/uhabits2.png
|
||||
[screen3th]: screenshots/thumbs/uhabits3.png
|
||||
[screen4th]: screenshots/thumbs/uhabits4.png
|
||||
## Features
|
||||
|
||||
* <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.
|
||||
|
||||
* <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.
|
||||
|
||||
* <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.
|
||||
|
||||
* <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.
|
||||
|
||||
* <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.
|
||||
|
||||
* <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.
|
||||
|
||||
* <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
|
||||
|
||||
The easiest way to install Loop is through the [Google Play Store][playstore] or [F-Droid][fdroid].
|
||||
You may also download and install the APK from the [releases page][releases];
|
||||
note, however, that the app will not be updated automatically. To build this
|
||||
app from the source code, see [build instructions][build].
|
||||
|
||||
## Contributing
|
||||
|
||||
Loop is an open source project developed entirely by volunteers. If you would
|
||||
like to contribute to the project, you are very welcome. There are many ways to
|
||||
contribute, even if you are not a software developer.
|
||||
|
||||
* **Report bugs, suggest features.** The easiest way to contribute is to simply
|
||||
use the app and let us know if you find any problems or have any suggestions
|
||||
to improve it. You can either use the link inside the app, or open an issue
|
||||
at GitHub. If you would like to receive the newest versions of the app
|
||||
earlier than everyone else, [join our open beta on Google Play][beta].
|
||||
|
||||
* **Spread the word.** If you like the app, share it with your family, friends
|
||||
and colleagues. You can also rate and review the app on Google Play Store, to help
|
||||
other users find it more easily.
|
||||
|
||||
* **Translate the app into your own language.** If you are not a native English
|
||||
speaker, and would like to see the app translated into your own language,
|
||||
please join our [open translation project][poedit]. If the translation
|
||||
is already completed, you are also very welcome to join and proofread it.
|
||||
|
||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||
contribute with code. Please, see the [developer guidelines][dev-guide] for more details.
|
||||
|
||||
## License
|
||||
|
||||
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||
|
||||
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 distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
[screen1]: screenshots/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
|
||||
[dev-guide]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines
|
||||
[build]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines#building
|
||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class AppContext
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.*;
|
||||
|
||||
import dagger.*;
|
||||
|
||||
@Module
|
||||
public class AppContextModule
|
||||
{
|
||||
private final Context context;
|
||||
|
||||
public AppContextModule(@AppContext Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppContext
|
||||
Context getContext()
|
||||
{
|
||||
return 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 javax.inject.*
|
||||
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ActivityContext
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 dagger.*;
|
||||
|
||||
@Module
|
||||
public class ActivityContextModule
|
||||
{
|
||||
private Context context;
|
||||
|
||||
public ActivityContextModule(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ActivityContext
|
||||
public Context getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 javax.inject.*
|
||||
|
||||
/**
|
||||
* Scope used by objects that live as long as the activity is alive.
|
||||
*/
|
||||
@Scope
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 dagger.*;
|
||||
|
||||
@Module
|
||||
public class BaseActivityModule
|
||||
{
|
||||
private BaseActivity activity;
|
||||
|
||||
public BaseActivityModule(BaseActivity activity)
|
||||
{
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public BaseActivity getBaseActivity()
|
||||
{
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.view.*
|
||||
import androidx.annotation.*
|
||||
|
||||
/**
|
||||
* Base class for all the menus in the application.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
abstract class BaseMenu(private val activity: BaseActivity) {
|
||||
|
||||
/**
|
||||
* Declare that the menu has changed, and should be recreated.
|
||||
*/
|
||||
fun invalidate() {
|
||||
activity.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
open fun onCreate(menu: Menu) {}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
fun onCreate(inflater: MenuInflater, menu: Menu) {
|
||||
menu.clear()
|
||||
inflater.inflate(getMenuResourceId(), menu)
|
||||
onCreate(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever an item on the menu is selected.
|
||||
*
|
||||
* @param item the item that was selected.
|
||||
* @return true if the event was consumed, or false otherwise
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.view.*
|
||||
import androidx.appcompat.view.ActionMode
|
||||
|
||||
/**
|
||||
* Base class for all the selection menus in the application.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This class hides many implementation details of creating such menus in
|
||||
* 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 [ActionMode].
|
||||
*/
|
||||
abstract class BaseSelectionMenu {
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
/**
|
||||
* Finishes the selection operation.
|
||||
*/
|
||||
fun finish() {
|
||||
actionMode?.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare that the menu has changed, and should be recreated.
|
||||
*/
|
||||
fun invalidate() {
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
open fun onFinish() {}
|
||||
|
||||
/**
|
||||
* Called whenever an item on the menu is selected.
|
||||
*
|
||||
* @param item the item that was selected.
|
||||
* @return true if the event was consumed, or false otherwise
|
||||
*/
|
||||
open fun onItemClicked(item: MenuItem): Boolean = false
|
||||
|
||||
/**
|
||||
* Called whenever the menu is invalidated.
|
||||
*
|
||||
* @param menu the menu to be refreshed
|
||||
* @return true if the menu has changes, false otherwise
|
||||
*/
|
||||
open fun onPrepare(menu: Menu): Boolean = false
|
||||
|
||||
/**
|
||||
* Sets the title of the selection menu.
|
||||
*
|
||||
* @param title the new title.
|
||||
*/
|
||||
fun setTitle(title: String?) {
|
||||
actionMode?.title = title
|
||||
}
|
||||
|
||||
protected abstract fun getResourceId(): Int
|
||||
|
||||
/**
|
||||
* Called when the menu is first created.
|
||||
*
|
||||
* @param menu the menu being created
|
||||
*/
|
||||
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>
|
||||
300
android/android-base/src/main/res/values/material_colors.xml
Normal file
@@ -0,0 +1,300 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="red_50">#FFEBEE</color>
|
||||
<color name="red_100">#FFCDD2</color>
|
||||
<color name="red_200">#EF9A9A</color>
|
||||
<color name="red_300">#E57373</color>
|
||||
<color name="red_400">#EF5350</color>
|
||||
<color name="red_500">#F44336</color>
|
||||
<color name="red_600">#E53935</color>
|
||||
<color name="red_700">#D32F2F</color>
|
||||
<color name="red_800">#C62828</color>
|
||||
<color name="red_900">#B71C1C</color>
|
||||
<color name="red_A100">#FF8A80</color>
|
||||
<color name="red_A200">#FF5252</color>
|
||||
<color name="red_A400">#FF1744</color>
|
||||
<color name="red_A700">#D50000</color>
|
||||
|
||||
<color name="deep_purple_50">#EDE7F6</color>
|
||||
<color name="deep_purple_100">#D1C4E9</color>
|
||||
<color name="deep_purple_200">#B39DDB</color>
|
||||
<color name="deep_purple_300">#9575CD</color>
|
||||
<color name="deep_purple_400">#7E57C2</color>
|
||||
<color name="deep_purple_500">#673AB7</color>
|
||||
<color name="deep_purple_600">#5E35B1</color>
|
||||
<color name="deep_purple_700">#512DA8</color>
|
||||
<color name="deep_purple_800">#4527A0</color>
|
||||
<color name="deep_purple_900">#311B92</color>
|
||||
<color name="deep_purple_A100">#B388FF</color>
|
||||
<color name="deep_purple_A200">#7C4DFF</color>
|
||||
<color name="deep_purple_A400">#651FFF</color>
|
||||
<color name="deep_purple_A700">#6200EA</color>
|
||||
|
||||
<color name="light_blue_50">#E1F5FE</color>
|
||||
<color name="light_blue_100">#B3E5FC</color>
|
||||
<color name="light_blue_200">#81D4FA</color>
|
||||
<color name="light_blue_300">#4FC3F7</color>
|
||||
<color name="light_blue_400">#29B6F6</color>
|
||||
<color name="light_blue_500">#03A9F4</color>
|
||||
<color name="light_blue_600">#039BE5</color>
|
||||
<color name="light_blue_700">#0288D1</color>
|
||||
<color name="light_blue_800">#0277BD</color>
|
||||
<color name="light_blue_900">#01579B</color>
|
||||
<color name="light_blue_A100">#80D8FF</color>
|
||||
<color name="light_blue_A200">#40C4FF</color>
|
||||
<color name="light_blue_A400">#00B0FF</color>
|
||||
<color name="light_blue_A700">#0091EA</color>
|
||||
|
||||
<color name="green_50">#E8F5E9</color>
|
||||
<color name="green_100">#C8E6C9</color>
|
||||
<color name="green_200">#A5D6A7</color>
|
||||
<color name="green_300">#81C784</color>
|
||||
<color name="green_400">#66BB6A</color>
|
||||
<color name="green_500">#4CAF50</color>
|
||||
<color name="green_600">#43A047</color>
|
||||
<color name="green_700">#388E3C</color>
|
||||
<color name="green_800">#2E7D32</color>
|
||||
<color name="green_900">#1B5E20</color>
|
||||
<color name="green_A100">#B9F6CA</color>
|
||||
<color name="green_A200">#69F0AE</color>
|
||||
<color name="green_A400">#00E676</color>
|
||||
<color name="green_A700">#00C853</color>
|
||||
|
||||
<color name="yellow_50">#FFFDE7</color>
|
||||
<color name="yellow_100">#FFF9C4</color>
|
||||
<color name="yellow_200">#FFF59D</color>
|
||||
<color name="yellow_300">#FFF176</color>
|
||||
<color name="yellow_400">#FFEE58</color>
|
||||
<color name="yellow_500">#FFEB3B</color>
|
||||
<color name="yellow_600">#FDD835</color>
|
||||
<color name="yellow_700">#FBC02D</color>
|
||||
<color name="yellow_800">#F9A825</color>
|
||||
<color name="yellow_900">#F57F17</color>
|
||||
<color name="yellow_A100">#FFFF8D</color>
|
||||
<color name="yellow_A200">#FFFF00</color>
|
||||
<color name="yellow_A400">#FFEA00</color>
|
||||
<color name="yellow_A700">#FFD600</color>
|
||||
|
||||
<color name="deep_orange_50">#FBE9E7</color>
|
||||
<color name="deep_orange_100">#FFCCBC</color>
|
||||
<color name="deep_orange_200">#FFAB91</color>
|
||||
<color name="deep_orange_300">#FF8A65</color>
|
||||
<color name="deep_orange_400">#FF7043</color>
|
||||
<color name="deep_orange_500">#FF5722</color>
|
||||
<color name="deep_orange_600">#F4511E</color>
|
||||
<color name="deep_orange_700">#E64A19</color>
|
||||
<color name="deep_orange_800">#D84315</color>
|
||||
<color name="deep_orange_900">#BF360C</color>
|
||||
<color name="deep_orange_A100">#FF9E80</color>
|
||||
<color name="deep_orange_A200">#FF6E40</color>
|
||||
<color name="deep_orange_A400">#FF3D00</color>
|
||||
<color name="deep_orange_A700">#DD2C00</color>
|
||||
|
||||
<color name="blue_grey_50">#ECEFF1</color>
|
||||
<color name="blue_grey_100">#CFD8DC</color>
|
||||
<color name="blue_grey_200">#B0BEC5</color>
|
||||
<color name="blue_grey_300">#90A4AE</color>
|
||||
<color name="blue_grey_400">#78909C</color>
|
||||
<color name="blue_grey_500">#607D8B</color>
|
||||
<color name="blue_grey_600">#546E7A</color>
|
||||
<color name="blue_grey_700">#455A64</color>
|
||||
<color name="blue_grey_800">#37474F</color>
|
||||
<color name="blue_grey_900">#263238</color>
|
||||
|
||||
<color name="pink_50">#FCE4EC</color>
|
||||
<color name="pink_100">#F8BBD0</color>
|
||||
<color name="pink_200">#F48FB1</color>
|
||||
<color name="pink_300">#F06292</color>
|
||||
<color name="pink_400">#EC407A</color>
|
||||
<color name="pink_500">#E91E63</color>
|
||||
<color name="pink_600">#D81B60</color>
|
||||
<color name="pink_700">#C2185B</color>
|
||||
<color name="pink_800">#AD1457</color>
|
||||
<color name="pink_900">#880E4F</color>
|
||||
<color name="pink_A100">#FF80AB</color>
|
||||
<color name="pink_A200">#FF4081</color>
|
||||
<color name="pink_A400">#F50057</color>
|
||||
<color name="pink_A700">#C51162</color>
|
||||
|
||||
<color name="indigo_50">#E8EAF6</color>
|
||||
<color name="indigo_100">#C5CAE9</color>
|
||||
<color name="indigo_200">#9FA8DA</color>
|
||||
<color name="indigo_300">#7986CB</color>
|
||||
<color name="indigo_400">#5C6BC0</color>
|
||||
<color name="indigo_500">#3F51B5</color>
|
||||
<color name="indigo_600">#3949AB</color>
|
||||
<color name="indigo_700">#303F9F</color>
|
||||
<color name="indigo_800">#283593</color>
|
||||
<color name="indigo_900">#1A237E</color>
|
||||
<color name="indigo_A100">#8C9EFF</color>
|
||||
<color name="indigo_A200">#536DFE</color>
|
||||
<color name="indigo_A400">#3D5AFE</color>
|
||||
<color name="indigo_A700">#304FFE</color>
|
||||
|
||||
<color name="cyan_50">#E0F7FA</color>
|
||||
<color name="cyan_100">#B2EBF2</color>
|
||||
<color name="cyan_200">#80DEEA</color>
|
||||
<color name="cyan_300">#4DD0E1</color>
|
||||
<color name="cyan_400">#26C6DA</color>
|
||||
<color name="cyan_500">#00BCD4</color>
|
||||
<color name="cyan_600">#00ACC1</color>
|
||||
<color name="cyan_700">#0097A7</color>
|
||||
<color name="cyan_800">#00838F</color>
|
||||
<color name="cyan_900">#006064</color>
|
||||
<color name="cyan_A100">#84FFFF</color>
|
||||
<color name="cyan_A200">#18FFFF</color>
|
||||
<color name="cyan_A400">#00E5FF</color>
|
||||
<color name="cyan_A700">#00B8D4</color>
|
||||
|
||||
<color name="light_green_50">#F1F8E9</color>
|
||||
<color name="light_green_100">#DCEDC8</color>
|
||||
<color name="light_green_200">#C5E1A5</color>
|
||||
<color name="light_green_300">#AED581</color>
|
||||
<color name="light_green_400">#9CCC65</color>
|
||||
<color name="light_green_500">#8BC34A</color>
|
||||
<color name="light_green_600">#7CB342</color>
|
||||
<color name="light_green_700">#689F38</color>
|
||||
<color name="light_green_800">#558B2F</color>
|
||||
<color name="light_green_900">#33691E</color>
|
||||
<color name="light_green_A100">#CCFF90</color>
|
||||
<color name="light_green_A200">#B2FF59</color>
|
||||
<color name="light_green_A400">#76FF03</color>
|
||||
<color name="light_green_A700">#64DD17</color>
|
||||
|
||||
<color name="amber_50">#FFF8E1</color>
|
||||
<color name="amber_100">#FFECB3</color>
|
||||
<color name="amber_200">#FFE082</color>
|
||||
<color name="amber_300">#FFD54F</color>
|
||||
<color name="amber_400">#FFCA28</color>
|
||||
<color name="amber_500">#FFC107</color>
|
||||
<color name="amber_600">#FFB300</color>
|
||||
<color name="amber_700">#FFA000</color>
|
||||
<color name="amber_800">#FF8F00</color>
|
||||
<color name="amber_900">#FF6F00</color>
|
||||
<color name="amber_A100">#FFE57F</color>
|
||||
<color name="amber_A200">#FFD740</color>
|
||||
<color name="amber_A400">#FFC400</color>
|
||||
<color name="amber_A700">#FFAB00</color>
|
||||
|
||||
<color name="brown_50">#EFEBE9</color>
|
||||
<color name="brown_100">#D7CCC8</color>
|
||||
<color name="brown_200">#BCAAA4</color>
|
||||
<color name="brown_300">#A1887F</color>
|
||||
<color name="brown_400">#8D6E63</color>
|
||||
<color name="brown_500">#795548</color>
|
||||
<color name="brown_600">#6D4C41</color>
|
||||
<color name="brown_700">#5D4037</color>
|
||||
<color name="brown_800">#4E342E</color>
|
||||
<color name="brown_900">#3E2723</color>
|
||||
|
||||
<color name="purple_50">#F3E5F5</color>
|
||||
<color name="purple_100">#E1BEE7</color>
|
||||
<color name="purple_200">#CE93D8</color>
|
||||
<color name="purple_300">#BA68C8</color>
|
||||
<color name="purple_400">#AB47BC</color>
|
||||
<color name="purple_500">#9C27B0</color>
|
||||
<color name="purple_600">#8E24AA</color>
|
||||
<color name="purple_700">#7B1FA2</color>
|
||||
<color name="purple_800">#6A1B9A</color>
|
||||
<color name="purple_900">#4A148C</color>
|
||||
<color name="purple_A100">#EA80FC</color>
|
||||
<color name="purple_A200">#E040FB</color>
|
||||
<color name="purple_A400">#D500F9</color>
|
||||
<color name="purple_A700">#AA00FF</color>
|
||||
|
||||
<color name="blue_50">#E3F2FD</color>
|
||||
<color name="blue_100">#BBDEFB</color>
|
||||
<color name="blue_200">#90CAF9</color>
|
||||
<color name="blue_300">#64B5F6</color>
|
||||
<color name="blue_400">#42A5F5</color>
|
||||
<color name="blue_500">#2196F3</color>
|
||||
<color name="blue_600">#1E88E5</color>
|
||||
<color name="blue_700">#1976D2</color>
|
||||
<color name="blue_800">#1565C0</color>
|
||||
<color name="blue_900">#0D47A1</color>
|
||||
<color name="blue_A100">#82B1FF</color>
|
||||
<color name="blue_A200">#448AFF</color>
|
||||
<color name="blue_A400">#2979FF</color>
|
||||
<color name="blue_A700">#2962FF</color>
|
||||
|
||||
<color name="teal_50">#E0F2F1</color>
|
||||
<color name="teal_100">#B2DFDB</color>
|
||||
<color name="teal_200">#80CBC4</color>
|
||||
<color name="teal_300">#4DB6AC</color>
|
||||
<color name="teal_400">#26A69A</color>
|
||||
<color name="teal_500">#009688</color>
|
||||
<color name="teal_600">#00897B</color>
|
||||
<color name="teal_700">#00796B</color>
|
||||
<color name="teal_800">#00695C</color>
|
||||
<color name="teal_900">#004D40</color>
|
||||
<color name="teal_A100">#A7FFEB</color>
|
||||
<color name="teal_A200">#64FFDA</color>
|
||||
<color name="teal_A400">#1DE9B6</color>
|
||||
<color name="teal_A700">#00BFA5</color>
|
||||
|
||||
<color name="lime_50">#F9FBE7</color>
|
||||
<color name="lime_100">#F0F4C3</color>
|
||||
<color name="lime_200">#E6EE9C</color>
|
||||
<color name="lime_300">#DCE775</color>
|
||||
<color name="lime_400">#D4E157</color>
|
||||
<color name="lime_500">#CDDC39</color>
|
||||
<color name="lime_600">#C0CA33</color>
|
||||
<color name="lime_700">#AFB42B</color>
|
||||
<color name="lime_800">#9E9D24</color>
|
||||
<color name="lime_900">#827717</color>
|
||||
<color name="lime_A100">#F4FF81</color>
|
||||
<color name="lime_A200">#EEFF41</color>
|
||||
<color name="lime_A400">#C6FF00</color>
|
||||
<color name="lime_A700">#AEEA00</color>
|
||||
|
||||
<color name="orange_50">#FFF3E0</color>
|
||||
<color name="orange_100">#FFE0B2</color>
|
||||
<color name="orange_200">#FFCC80</color>
|
||||
<color name="orange_300">#FFB74D</color>
|
||||
<color name="orange_400">#FFA726</color>
|
||||
<color name="orange_500">#FF9800</color>
|
||||
<color name="orange_600">#FB8C00</color>
|
||||
<color name="orange_700">#F57C00</color>
|
||||
<color name="orange_800">#EF6C00</color>
|
||||
<color name="orange_900">#E65100</color>
|
||||
<color name="orange_A100">#FFD180</color>
|
||||
<color name="orange_A200">#FFAB40</color>
|
||||
<color name="orange_A400">#FF9100</color>
|
||||
<color name="orange_A700">#FF6D00</color>
|
||||
|
||||
<color name="grey_50">#FAFAFA</color>
|
||||
<color name="grey_100">#F5F5F5</color>
|
||||
<color name="grey_200">#EEEEEE</color>
|
||||
<color name="grey_300">#E0E0E0</color>
|
||||
<color name="grey_400">#BDBDBD</color>
|
||||
<color name="grey_500">#9E9E9E</color>
|
||||
<color name="grey_600">#757575</color>
|
||||
<color name="grey_700">#616161</color>
|
||||
<color name="grey_750">#525252</color>
|
||||
<color name="grey_800">#424242</color>
|
||||
<color name="grey_850">#303030</color>
|
||||
<color name="grey_875">#282828</color>
|
||||
<color name="grey_900">#212121</color>
|
||||
<color name="grey_950">#101010</color>
|
||||
|
||||
<color name="white">#ffffff</color>
|
||||
<color name="black">#000000</color>
|
||||
|
||||
<color name="black_ae">#ef000000</color>
|
||||
<color name="black_ac">#cf000000</color>
|
||||
<color name="black_aa">#af000000</color>
|
||||
<color name="black_a8">#8f000000</color>
|
||||
<color name="black_a6">#6f000000</color>
|
||||
<color name="black_a4">#4f000000</color>
|
||||
<color name="black_a2">#2f000000</color>
|
||||
<color name="black_a0">#0f000000</color>
|
||||
|
||||
<color name="white_ae">#efffffff</color>
|
||||
<color name="white_ac">#cfffffff</color>
|
||||
<color name="white_aa">#afffffff</color>
|
||||
<color name="white_a8">#8fffffff</color>
|
||||
<color name="white_a6">#6fffffff</color>
|
||||
<color name="white_a4">#4fffffff</color>
|
||||
<color name="white_a2">#2fffffff</color>
|
||||
<color name="white_a0">#0fffffff</color>
|
||||
</resources>
|
||||
23
android/android-base/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016-2020 Á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/>.
|
||||
-->
|
||||
|
||||
<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,24 +16,21 @@
|
||||
|
||||
package com.android.colorpicker;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import android.app.*;
|
||||
import android.os.*;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
|
||||
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
|
||||
* select a specific color swatch, which invokes a listener.
|
||||
*/
|
||||
public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
|
||||
public class ColorPickerDialog extends AppCompatDialogFragment implements OnColorSelectedListener {
|
||||
|
||||
public static final int SIZE_LARGE = 1;
|
||||
public static final int SIZE_SMALL = 2;
|
||||
@@ -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,39 +16,33 @@
|
||||
|
||||
package com.android.datetimepicker.time;
|
||||
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import android.animation.*;
|
||||
import android.annotation.*;
|
||||
import android.app.ActionBar.*;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.os.*;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import androidx.appcompat.app.*;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.ActionBar.LayoutParams;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
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.util.*;
|
||||
import android.view.*;
|
||||
import android.view.View.*;
|
||||
import android.widget.*;
|
||||
|
||||
import com.android.datetimepicker.HapticFeedbackController;
|
||||
import com.android.datetimepicker.Utils;
|
||||
import com.android.datetimepicker.time.RadialPickerLayout.OnValueSelectedListener;
|
||||
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 DialogFragment implements OnValueSelectedListener{
|
||||
public class TimePickerDialog extends AppCompatDialogFragment implements OnValueSelectedListener
|
||||
{
|
||||
private static final String TAG = "TimePickerDialog";
|
||||
|
||||
private static final String KEY_HOUR_OF_DAY = "hour_of_day";
|
||||
@@ -58,6 +52,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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;
|
||||
@@ -72,6 +67,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
private static final int PULSE_ANIMATOR_DELAY = 300;
|
||||
|
||||
private OnTimeSetListener mCallback;
|
||||
private DialogInterface.OnDismissListener dismissListener;
|
||||
|
||||
private HapticFeedbackController mHapticFeedbackController;
|
||||
|
||||
@@ -116,36 +112,50 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
* 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;
|
||||
@@ -153,47 +163,61 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
return new AppCompatDialog(getActivity(), R.style.TimePickerDialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
View view = inflater.inflate(R.layout.time_picker_dialog, null);
|
||||
KeyboardListener keyboardListener = new KeyboardListener();
|
||||
@@ -204,8 +228,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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);
|
||||
@@ -224,8 +248,9 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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 &&
|
||||
@@ -235,25 +260,31 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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 {
|
||||
@@ -261,7 +292,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
if (mCallback != null) {
|
||||
mCallback.onTimeSet(mTimePicker,
|
||||
mTimePicker.getHours(), mTimePicker.getMinutes());
|
||||
mTimePicker.getHours(), mTimePicker.getMinutes());
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
@@ -270,16 +301,16 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|
||||
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.
|
||||
@@ -294,15 +325,17 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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);
|
||||
@@ -329,56 +362,61 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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);
|
||||
@@ -388,7 +426,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
|
||||
@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());
|
||||
@@ -399,6 +438,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
outState.putIntegerArrayList(KEY_TYPED_TIMES, mTypedTimes);
|
||||
}
|
||||
outState.putBoolean(KEY_DARK_THEME, mThemeDark);
|
||||
outState.putInt(KEY_SELECTED_COLOR, mSelectedColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +446,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
* 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);
|
||||
@@ -418,7 +459,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -431,7 +472,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
private void setHour(int value, boolean announce) {
|
||||
private void setHour(int value, boolean announce)
|
||||
{
|
||||
String format;
|
||||
if (mIs24HourMode) {
|
||||
format = "%02d";
|
||||
@@ -451,7 +493,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
private void setMinute(int value) {
|
||||
private void setMinute(int value)
|
||||
{
|
||||
if (value == 60) {
|
||||
value = 0;
|
||||
}
|
||||
@@ -463,7 +506,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|
||||
// Show either Hours or Minutes.
|
||||
private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
|
||||
boolean announce) {
|
||||
boolean announce)
|
||||
{
|
||||
mTimePicker.setCurrentItemShowing(index, animateCircle);
|
||||
|
||||
TextView labelToAnimate;
|
||||
@@ -486,8 +530,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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);
|
||||
|
||||
@@ -500,15 +544,17 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -523,7 +569,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
if (mCallback != null) {
|
||||
mCallback.onTimeSet(mTimePicker,
|
||||
mTimePicker.getHours(), mTimePicker.getMinutes());
|
||||
mTimePicker.getHours(), mTimePicker.getMinutes());
|
||||
}
|
||||
dismiss();
|
||||
return true;
|
||||
@@ -540,7 +586,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
|
||||
}
|
||||
Utils.tryAccessibilityAnnounce(mTimePicker,
|
||||
String.format(mDeletedKeyFormat, deletedKeyStr));
|
||||
String.format(mDeletedKeyFormat, deletedKeyStr));
|
||||
updateDisplay(true);
|
||||
}
|
||||
}
|
||||
@@ -550,7 +596,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|| 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.
|
||||
@@ -573,11 +619,13 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
/**
|
||||
* 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;
|
||||
@@ -586,7 +634,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
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) ||
|
||||
@@ -618,7 +667,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
* 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);
|
||||
@@ -632,7 +682,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
/**
|
||||
* 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.
|
||||
@@ -646,7 +697,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
private int deleteLastTypedKey() {
|
||||
private int deleteLastTypedKey()
|
||||
{
|
||||
int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
|
||||
if (!isTypedTimeFullyLegal()) {
|
||||
mDoneButton.setEnabled(false);
|
||||
@@ -656,9 +708,11 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -678,29 +732,31 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
* 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);
|
||||
@@ -713,7 +769,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
private static int getValFromKeyCode(int keyCode) {
|
||||
private static int getValFromKeyCode(int keyCode)
|
||||
{
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_0:
|
||||
return 0;
|
||||
@@ -742,20 +799,22 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -766,15 +825,15 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
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;
|
||||
}
|
||||
@@ -788,7 +847,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
/**
|
||||
* 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.
|
||||
@@ -823,7 +883,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
/**
|
||||
* 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;
|
||||
@@ -879,7 +940,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
// 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.
|
||||
@@ -956,20 +1017,24 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
* 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;
|
||||
@@ -978,7 +1043,8 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
return false;
|
||||
}
|
||||
|
||||
public Node canReach(int key) {
|
||||
public Node canReach(int key)
|
||||
{
|
||||
if (mChildren == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -991,13 +1057,28 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
149
android/android-pickers/src/main/res/values/pickers.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<dimen name="color_swatch_large">64dip</dimen>
|
||||
<dimen name="color_swatch_small">48dip</dimen>
|
||||
<dimen name="color_swatch_margins_large">8dip</dimen>
|
||||
<dimen name="color_swatch_margins_small">4dip</dimen>
|
||||
|
||||
<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>
|
||||
<dimen name="ampm_label_size">16sp</dimen>
|
||||
<dimen name="done_label_size">14sp</dimen>
|
||||
<dimen name="ampm_left_padding">6dip</dimen>
|
||||
<dimen name="separator_padding">4dip</dimen>
|
||||
<dimen name="header_height">80dip</dimen>
|
||||
<dimen name="footer_height">48dip</dimen>
|
||||
<dimen name="minimum_margin_sides">48dip</dimen>
|
||||
<dimen name="minimum_margin_top_bottom">24dip</dimen>
|
||||
<dimen name="picker_dimen">250dip</dimen>
|
||||
<dimen name="date_picker_component_width">250dp</dimen>
|
||||
<dimen name="date_picker_header_height">30dp</dimen>
|
||||
<dimen name="selected_calendar_layout_height">155dp</dimen>
|
||||
<dimen name="date_picker_view_animator_height">270dp</dimen>
|
||||
<dimen name="done_button_height">42dp</dimen>
|
||||
<dimen name="month_list_item_header_height">50dp</dimen>
|
||||
<dimen name="month_day_label_text_size">10sp</dimen>
|
||||
<dimen name="day_number_select_circle_radius">16dp</dimen>
|
||||
<dimen name="month_select_circle_radius">45dp</dimen>
|
||||
<dimen name="selected_date_year_size">30dp</dimen>
|
||||
<dimen name="selected_date_day_size">75dp</dimen>
|
||||
<dimen name="selected_date_month_size">30dp</dimen>
|
||||
<dimen name="date_picker_header_text_size">14dp</dimen>
|
||||
<dimen name="month_label_size">16sp</dimen>
|
||||
<dimen name="day_number_size">16sp</dimen>
|
||||
<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 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>
|
||||
<string name="minute_picker_description" translatable="false">Minutes circular slider</string>
|
||||
<string name="day_picker_description" translatable="false">Month grid of days</string>
|
||||
<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 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 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
#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-2.2.1-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"
|
||||
}
|
||||