Compare commits
1384 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2163a2b93b | |||
| afad56ab91 | |||
| ece1b93f8d | |||
| 10416e40fa | |||
| 88f8581acc | |||
| b33dd2a994 | |||
| d87961d800 | |||
| 38d2606d6d | |||
| 0a91c097e8 | |||
|
|
b1c53bd820 | ||
| c973f93424 | |||
| fcadbe7c38 | |||
| 89e3dd7655 | |||
| df56608a5d | |||
| 3489f176d6 | |||
| 75fdd9d2b1 | |||
| dd1999681f | |||
| 63883032c4 | |||
| a8c9b1b71c | |||
| 61529fde4d | |||
| 1050cc4e47 | |||
| a7845c9290 | |||
| 1feefe107c | |||
| 1654ff9af3 | |||
| 58adff6a22 | |||
| 2728c311d8 | |||
| b561dfe90d | |||
| 53a40b2cfa | |||
| 98ce802f87 | |||
| 853b4275be | |||
| df4355c072 | |||
| 33c3feb11a | |||
| f88a9bd46a | |||
| 5dd9f43464 | |||
| 4a13623b2c | |||
| 6b368bffdf | |||
| 8d87988871 | |||
| 256ca13dca | |||
| b0c5552cfa | |||
| 616ab4861d | |||
| fcff067b2e | |||
| da018fc64d | |||
| f27a9f9103 | |||
|
|
fe59565ded | ||
|
|
419d2faa78 | ||
|
|
55f3bab13a | ||
|
|
212cc4259b | ||
|
|
e1ec8cfa10 | ||
|
|
6af2edb690 | ||
|
|
9d6b3ebb25 | ||
|
|
ed0744d5ab | ||
|
|
b59886dc77 | ||
|
|
23954eed27 | ||
| 071b6bd090 | |||
| 298bf1a538 | |||
| 8b2285787c | |||
| c83cd5fd10 | |||
| 381c67d020 | |||
| 81cffc7253 | |||
| e745b959ca | |||
|
|
d2ee210376 | ||
|
|
9c81f22a4d | ||
| 51947576aa | |||
|
|
6411f65fa5 | ||
|
|
a04349bfaa | ||
| 7b68938e14 | |||
| ee462715de | |||
| a95da72e19 | |||
| 3ece80f3da | |||
|
|
e00ff99af7 | ||
|
|
fc26d7967f | ||
| 9b753b07f8 | |||
|
|
73deb535ef | ||
|
|
a733e6ddf0 | ||
|
|
86c7200e6b | ||
|
|
15282e7ea6 | ||
|
|
96f87df052 | ||
|
|
bc623f044d | ||
|
|
773eef26e7 | ||
| b58af03a7c | |||
| c53997ffcc | |||
| 11d55bbf02 | |||
|
|
50ebff3edf | ||
| 8e1aa3d01e | |||
|
|
ea66cf04b3 | ||
|
|
b1da72dc3a | ||
|
|
91bd17bb9e | ||
| fca9557108 | |||
|
|
d76a4a84d0 | ||
|
|
8790c0e87b | ||
|
|
bc461e1239 | ||
| 589fd2f0f1 | |||
|
|
2e94bebcac | ||
|
|
ff748b27a5 | ||
|
|
8522318ed1 | ||
|
|
1f08d2f71c | ||
|
|
7fb3489ca8 | ||
| 129c86a030 | |||
| e4121b2564 | |||
| 98abebe099 | |||
| 0f86cb4d53 | |||
|
|
4dd77463fb | ||
|
|
2cf3347f8c | ||
| 5516f40571 | |||
| c006659ba4 | |||
| 5be2c51d79 | |||
|
|
2dcce080d3 | ||
|
|
6c3b4135c2 | ||
| 48c1adb3bb | |||
| 89bde4c9ae | |||
| a7e2e0e8da | |||
| 64d77fbb6b | |||
|
|
4b80e60b2b | ||
|
|
0f80505735 | ||
|
|
c26c140310 | ||
|
|
c05e008569 | ||
| a5255690ed | |||
| 56f0bbb7bd | |||
| 4e72bf93f8 | |||
| d31e42446e | |||
|
|
8ea71dc2b5 | ||
| 3ba26ac67e | |||
|
|
8537e0f910 | ||
| a6c9ea22e6 | |||
| 5767817de2 | |||
| b80344a443 | |||
| f36bc788c7 | |||
|
|
22136774de | ||
|
|
be39237a22 | ||
|
|
836eebd20b | ||
|
|
8d2aebaf7f | ||
|
|
1c2efb6c22 | ||
|
|
784877871a | ||
|
|
4a2fb2ac47 | ||
|
|
7caf22bd7a | ||
|
|
2d943b5942 | ||
|
|
dd4e0ee6f9 | ||
|
|
bb8d4abb9e | ||
|
|
64069a6e7f | ||
|
|
9ead803b6c | ||
|
|
af6387f1fc | ||
|
|
6d0764bbb8 | ||
|
|
eb9d017882 | ||
|
|
ec630ad1b1 | ||
|
|
df30c7c764 | ||
|
|
7a73f9c8c7 | ||
| 045612c00f | |||
| 25d3587545 | |||
| 4ffd78545d | |||
|
|
14d018c327 | ||
|
|
f1c88797a3 | ||
|
|
f882e18be9 | ||
|
|
0fc9bb57ae | ||
|
|
22dcd9f7ae | ||
|
|
7d361b2203 | ||
|
|
8131d37d8e | ||
|
|
6485c3efee | ||
|
|
2bfbff9b14 | ||
|
|
9a72141567 | ||
|
|
6992b5186e | ||
|
|
18db571507 | ||
|
|
09794ccb68 | ||
|
|
7644f52dfd | ||
|
|
21fa636e0c | ||
|
|
df755d30ee | ||
|
|
7f0c4626b0 | ||
|
|
ec42fda336 | ||
|
|
def9ff9746 | ||
|
|
a7df0bde3e | ||
|
|
1cec5a6067 | ||
|
|
a58cbffb81 | ||
|
|
30630c3358 | ||
|
|
98f9693cff | ||
|
|
228be95f9c | ||
|
|
457c58a660 | ||
|
|
39cec6f11d | ||
|
|
8f5f72d9fd | ||
|
|
b21eb3f118 | ||
|
|
136ec5b49b | ||
|
|
e84cc8e8b1 | ||
|
|
26fb76f95f | ||
|
|
68a8f9d356 | ||
|
|
f541f47476 | ||
|
|
9d4cfbd270 | ||
| 1869d05591 | |||
| 3f8b1008b4 | |||
| e671949dd2 | |||
|
|
dedeb13f46 | ||
|
|
9d0fbb9ea9 | ||
|
|
97dcf98e2b | ||
|
|
25b25acc94 | ||
|
|
ac6df47818 | ||
|
|
0f828cbd3a | ||
|
|
eb8d39fbe3 | ||
|
|
11831a2b24 | ||
|
|
e9816a22a3 | ||
|
|
8165b5417f | ||
|
|
714771fbc3 | ||
|
|
a2a8dc4489 | ||
|
|
bbe39f8523 | ||
|
|
a69490a23a | ||
|
|
b6223c3805 | ||
| e3524c112a | |||
| 8c0141508d | |||
| 8206450456 | |||
| a5dfe0ec51 | |||
| 9a4aaf47bc | |||
|
|
485e4245ff | ||
|
|
72a7cd9685 | ||
|
|
95a7c4f474 | ||
|
|
f208601bc4 | ||
|
|
d5b9fda636 | ||
| 99277491c8 | |||
| 2828dfcc75 | |||
| b14aa668c2 | |||
| 47de6243d3 | |||
| 7bad2c886b | |||
| c4e496a5ff | |||
| 7ee3e00e49 | |||
| 2f39b63723 | |||
| d279388884 | |||
|
|
358e1ccf42 | ||
| 236d56bbf0 | |||
| 68f9b8339f | |||
| 4a4356b72a | |||
| b71675585c | |||
| bebb356425 | |||
| 9fd36d8d53 | |||
| 1137088e20 | |||
| 6f2b2ab883 | |||
| d8fcc9160d | |||
| cb6843e08b | |||
| 162eac3bdf | |||
| e2d2b5b4b3 | |||
| de3668db96 | |||
| 93a2ec3186 | |||
| 354c930d85 | |||
| e97cdce467 | |||
| 13826f4934 | |||
| 1cdbc53dc5 | |||
| 51b9517897 | |||
| a74a4b390b | |||
| 79b134164a | |||
| ced78e0b1f | |||
| 9087025418 | |||
| 9a5263e508 | |||
| 75571e2eb0 | |||
| b5ed4b9a9c | |||
| 2daf08d22d | |||
| 2e51c73ac0 | |||
| 298bb01762 | |||
| a02c7bdc44 | |||
| feb3c98459 | |||
| 823d8bed7e | |||
| 68813580eb | |||
| 6bc1b91cc6 | |||
| 56f91526e6 | |||
| dbc6a16a98 | |||
| 4d3c5ad732 | |||
| 307ff5c1ca | |||
| 3b86a17b49 | |||
| 54fe849eef | |||
| c023711d16 | |||
| 9bc0f44777 | |||
| 4a3a767cb2 | |||
| d0c37fef67 | |||
| 11863cd7b0 | |||
| 1c696e2561 | |||
| 43c5e450a8 | |||
| 0a84d8a1d8 | |||
| 354a4d08be | |||
| af93c0e488 | |||
| b8aa322691 | |||
| a87987da87 | |||
| 8b8e9d3980 | |||
| a19f39308e | |||
| 0b23fbed86 | |||
| d1eeb559cb | |||
| 2e4a82418f | |||
| fcdb400edf | |||
|
|
f80fb35e35 | ||
| 056a7b722b | |||
| f4c0f353f3 | |||
| 3fb7fbac51 | |||
| bd70746278 | |||
| c744d945a4 | |||
| f0696b3f60 | |||
| bc36f24551 | |||
| 82b68b5f4e | |||
| e8238b5ed7 | |||
| 599f21fabb | |||
| e91e2c1c96 | |||
| 9f421bfd6d | |||
| 3a4071505e | |||
| 344c4a324c | |||
| 0aca118546 | |||
| 124c490f25 | |||
| 0a5622c78e | |||
| 373f21e247 | |||
| e80292e75d | |||
| 7173381d9b | |||
| 78f31a65d4 | |||
| 2e19fee83c | |||
| f6754ff180 | |||
| 3f74c77755 | |||
| c904e22c0f | |||
| 27023e50ae | |||
| 0f2f010f94 | |||
| cf2989d587 | |||
| 94c78ebb72 | |||
| d9be39b839 | |||
| 7fcad7b5c5 | |||
| cfeef3651f | |||
| e849e8a5c2 | |||
| 5bd21e68df | |||
| 71597ca89b | |||
| a3cf2877b8 | |||
| 23e9ccb236 | |||
| 7d97554cb1 | |||
| 5f8c0d67b8 | |||
| b468325f64 | |||
| 39d23424d6 | |||
| eba2dd0171 | |||
| 1bdd44085c | |||
| 596af6afe6 | |||
| 3f3ddc3955 | |||
| a58c60b8ce | |||
| b42ad0dd83 | |||
| e0fbe841c1 | |||
| 3523b2c1a3 | |||
| c15c14ffcd | |||
| 8cd729480f | |||
| 638bc2816f | |||
| 826ea17cdd | |||
| 19e221bb32 | |||
| 85de69bca7 | |||
| 7d5b9d0f63 | |||
| 816ab71d83 | |||
| c230b5c40d | |||
| 71620a320d | |||
| c016b0aecc | |||
| 95b3028c95 | |||
| ae4ac801a0 | |||
| bda3e42e2b | |||
| 4dc1df5887 | |||
| 302b9f5df4 | |||
| c2d89c7a60 | |||
| 45574753c7 | |||
| c84c618ef0 | |||
| 98d17d60a5 | |||
| 4c1c6e76fc | |||
| ac1441aba5 | |||
| 5d2ff40dc9 | |||
| 63f2b80515 | |||
| 52b25fbc72 | |||
| b94a887534 | |||
| f6699fbfda | |||
| 9743b05a78 | |||
| 9681b7b5ef | |||
| 017bc50698 | |||
| 1e94456a74 | |||
| 2fcba80f3a | |||
| fc4cbe84f0 | |||
| 011abf62a0 | |||
|
|
9c96857262 | ||
| 4f55ec6ea8 | |||
| 8139088b39 | |||
| 44f7d13449 | |||
| 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 | |||
| ec4a381d70 | |||
| b13f2b4228 | |||
| efc7b2cebb | |||
| add08d6054 | |||
| 5d8a348aaf | |||
| ec0e8ac24c | |||
| fee3137a6f | |||
| abe6b10964 | |||
| 6484b96e5a | |||
| 3d3d5b9b96 | |||
| 14364901ff | |||
| 440706882b | |||
| ca9d56e59e | |||
| 9a6dafaa79 | |||
| 9a44774284 | |||
| 2b23b36e36 | |||
| 78d4f86cab | |||
| 18e8390aed | |||
| cabcd5b1bf | |||
| 7e8a2a0c1c | |||
| 650971bf36 | |||
| 3ffa079e24 | |||
| bb950d61fc | |||
| 0ad0e5cf36 | |||
| 3b56d6d596 | |||
| 8d57273987 | |||
| b98853ab26 | |||
| 071cad73d4 | |||
| 16dc9c25d2 | |||
| 6b23858136 | |||
| e0198e9926 | |||
| 6445bf62bc | |||
| 83ef8564e1 | |||
| e6e80b9841 | |||
| 442c3ed78d | |||
| ca9745f550 | |||
| 605593d739 | |||
| bfbfcbd8cf | |||
| 2399dccddc | |||
| dd5f37290c | |||
| dbf569ff87 | |||
| 5b402478e9 | |||
| e3b7e9f60f | |||
| b0040bd83c | |||
| 56e1268f85 | |||
| 41d9e2f0f5 | |||
| 1fcfb9b22e | |||
| 2c80544aaa |
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report something broken in the app
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Pre-submission checklist
|
||||
- [ ] I am submitting a bug report, not a feature request.
|
||||
- [ ] I am running the latest version of Loop Habit Tracker.
|
||||
- [ ] I have have searched for similar issues, but did not find any matches.
|
||||
|
||||
## Description
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
## Steps to reproduce
|
||||
1. Go to ...
|
||||
2. Click on ....
|
||||
3. Scroll down to ....
|
||||
4. See error
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## System information
|
||||
- Phone: [e.g. Google Pixel 4]
|
||||
- Phone Operating System: [e.g. Android 10]
|
||||
- App version: [e.g. 1.8.9]
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature requests
|
||||
url: https://github.com/iSoron/uhabits/discussions/categories/feature-requests?discussions_q=category%3A%22Feature+Requests%22+sort%3Atop
|
||||
about: Submit ideas for new features and for improving existing features
|
||||
- name: Help & FAQ
|
||||
url: https://github.com/iSoron/uhabits/discussions/categories/help-faq
|
||||
about: Ask questions about using the app or setting up the project
|
||||
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
60
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
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 Project
|
||||
run: ./build.sh build
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: uhabits-android
|
||||
path: uhabits-android/build/outputs/
|
||||
|
||||
AndroidTest:
|
||||
needs: Build
|
||||
runs-on: macOS-10.15
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
api: [
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
# 29, # Crashes constantly, see: https://issuetracker.google.com/issues/159732638
|
||||
# 30, # Not available yet
|
||||
# 31, # Not available yet
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Download Previously Built APK
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: uhabits-android
|
||||
path: uhabits-android/build/outputs/
|
||||
|
||||
- name: Run Android Tests
|
||||
run: ./build.sh android-tests ${{ matrix.api }}
|
||||
|
||||
58
.gitignore
vendored
@@ -1,36 +1,24 @@
|
||||
#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
|
||||
|
||||
art/
|
||||
*.actual.png
|
||||
*.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
|
||||
crowdin.yml
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "libs/drag-sort-listview"]
|
||||
path = libs/drag-sort-listview
|
||||
url = https://github.com/iSoron/drag-sort-listview.git
|
||||
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
338
CHANGELOG.md
@@ -1,107 +1,289 @@
|
||||
# Changelog
|
||||
|
||||
### 1.5.4 (May 29, 2016)
|
||||
## [2.0.2] - 2021-05-23
|
||||
|
||||
* Fix crash upon opening settings screen in some phones
|
||||
* Fix missing folders in CSV archive
|
||||
* Add Serbian translation
|
||||
### Changed
|
||||
- Make checkmark widget resizable
|
||||
|
||||
### 1.5.3 (May 22, 2016)
|
||||
### Fixed
|
||||
- Fix crash caused by numerical habits with zero target (@iSoron, #903)
|
||||
- Fix small issues with font size (@iSoron)
|
||||
- Allow fractional target values (@sumanabhi, #911)
|
||||
- Fix IllegalStateException in androidx.customview.view (@iSoron, #906)
|
||||
- Fix crash when selecting habit frequency in some languages (@iSoron, #926)
|
||||
- Fix IllegalArgumentException in RingView (@iSoron, #904)
|
||||
|
||||
* Complete Arabic and Czech translations
|
||||
* Fix crash at startup
|
||||
* Fix checkmark widget on custom launchers
|
||||
## [2.0.1] - 2021-05-09
|
||||
|
||||
### 1.5.2 (May 19, 2016)
|
||||
### Added
|
||||
- Make midnight delay optional and disabled by default (@hiqua)
|
||||
- Add arrows to sort menu (@iSoron)
|
||||
|
||||
* Fix missing attachment on bug reports
|
||||
* Fix bug that prevents some widgets from rendering
|
||||
* Complete Japanese translation
|
||||
### Removed
|
||||
- Temporarily remove experimental device sync functionality. This feature will be re-added in
|
||||
Loop 2.1.
|
||||
|
||||
### 1.5.1 (May 17, 2016)
|
||||
### Changed
|
||||
- Make implicit checkmarks easier to read (@iSoron)
|
||||
- Update and improve list of translators (@hiqua, @iSoron)
|
||||
|
||||
* Fix build on F-Droid
|
||||
### Fixed
|
||||
- Disable transparency for stacked widgets (@hiqua)
|
||||
- Fix various color issues on the dark theme (@hiqua, @iSoron)
|
||||
- Fix "customize notifications" on older devices (@hiqua)
|
||||
- Fix snooze button in notifications when device is locked (@hiqua)
|
||||
- Fix a crash when deleting habits (@engineering4good)
|
||||
- Fix checkmark widget not rendering properly on some Samsung phones (@iSoron)
|
||||
|
||||
### 1.5.0 (May 15, 2016)
|
||||
### Refactoring & Testing
|
||||
- Finish conversion of the entire project to Kotlin (@hiqua, @iSoron, @MarKco)
|
||||
- Automatically run large tests on GitHub Actions (@iSoron)
|
||||
- Remove unused v21 resources (@hiqua)
|
||||
|
||||
* 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
|
||||
## [2.0.0-alpha] - 2020-11-29
|
||||
|
||||
### 1.4.1 (April 9, 2016)
|
||||
### Added
|
||||
- Track numeric 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)
|
||||
|
||||
* Show error message on widgets, instead of crashing
|
||||
* Complete French translation
|
||||
* Minor fixes to other translations
|
||||
### Removed
|
||||
- Drop support to devices older than Android 6.0 (API 23)
|
||||
|
||||
### 1.4.0 (April 7, 2016)
|
||||
### Fixed
|
||||
- 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)
|
||||
|
||||
* 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
|
||||
### Refactoring
|
||||
- Convert files to Kotlin (@olegivo)
|
||||
|
||||
### 1.3.3 (March 20, 2016)
|
||||
## [1.8.12] - 2021-01-30
|
||||
|
||||
* Add Spanish and Korean translations
|
||||
* Make small corrections to other translations
|
||||
* Fix incorrect date in history calendar
|
||||
- Fix bug that caused incorrect check marks to show after scrolling (#713)
|
||||
- Fix issue preventing widgets from updating at midnight (#680)
|
||||
|
||||
### 1.3.2 (March 18, 2016)
|
||||
## [1.8.11] - 2020-12-29
|
||||
|
||||
* Add Arabic, Italian, Polish, Russian and Swedish translations
|
||||
* Minor fixes to German and French translations
|
||||
* Minor bug fixes
|
||||
- Fix theme issues on Xiaomi phones
|
||||
|
||||
### 1.3.1 (March 15, 2016)
|
||||
## [1.8.10] - 2020-11-26
|
||||
|
||||
* 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
|
||||
- Update translations
|
||||
|
||||
### 1.3.0 (March 12, 2016)
|
||||
## [1.8.9] - 2020-11-18
|
||||
|
||||
* 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
|
||||
- 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.2.0 (March 4, 2016)
|
||||
## [1.8.8] - 2020-06-21
|
||||
|
||||
* 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
|
||||
- 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.1.1 (February 24, 2016)
|
||||
## [1.8.0] - 2020-01-01
|
||||
|
||||
* 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
|
||||
- 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.0.0 (February 19, 2016)
|
||||
## [1.7.11] - 2019-08-10
|
||||
|
||||
* Initial release
|
||||
- Fix bug that produced corrupted CSV files in some countries
|
||||
|
||||
## [1.7.10] - 2019-06-15
|
||||
|
||||
- Fix bug that prevented some devices from showing notifications.
|
||||
- Update targetSdk to Android Pie (API level 28)
|
||||
|
||||
## [1.7.8] - 2018-04-21
|
||||
|
||||
- Add support for adaptive icons (Oreo)
|
||||
- Add support for notification channels (Oreo)
|
||||
- Update translations
|
||||
|
||||
## [1.7.7] - 2017-09-30
|
||||
|
||||
- Fix bug that caused reminders to show repeatedly on DST changes
|
||||
|
||||
## [1.7.6] - 2017-07-18
|
||||
|
||||
- Fix bug that caused widgets not to render sometimes
|
||||
- Fix other minor bugs
|
||||
- Update translations
|
||||
|
||||
## [1.7.3] - 2017-05-30
|
||||
|
||||
- Improve performance of 'sort by score'
|
||||
- Other minor bug fixes
|
||||
|
||||
## [1.7.2] - 2017-05-27
|
||||
|
||||
- Fix crash at startup
|
||||
|
||||
## [1.7.1] - 2017-05-21
|
||||
|
||||
- Fix crash (BadParcelableException)
|
||||
- Fix layout for RTL languages such as Arabic
|
||||
- Automatically detect and reject invalid database files
|
||||
- Add Hebrew translation
|
||||
|
||||
## [1.7.0] - 2017-03-31
|
||||
|
||||
- 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] - 2016-10-13
|
||||
|
||||
- Fix crash on Android 4.1
|
||||
|
||||
## [1.6.1] - 2016-10-10
|
||||
|
||||
- Fix a crash at startup when database is corrupted
|
||||
|
||||
## [1.6.0] - 2016-10-10
|
||||
|
||||
- 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] - 2016-06-19
|
||||
|
||||
- Fix bug that prevented checkmark widget from working
|
||||
|
||||
## [1.5.5] - 2016-06-19
|
||||
|
||||
- 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] - 2016-05-29
|
||||
|
||||
- Fix crash upon opening settings screen in some phones
|
||||
- Fix missing folders in CSV archive
|
||||
- Add Serbian translation
|
||||
|
||||
## [1.5.3] - 2016-05-22
|
||||
|
||||
- Complete Arabic and Czech translations
|
||||
- Fix crash at startup
|
||||
- Fix checkmark widget on custom launchers
|
||||
|
||||
## [1.5.2] - 2016-05-19
|
||||
|
||||
- Fix missing attachment on bug reports
|
||||
- Fix bug that prevents some widgets from rendering
|
||||
- Complete Japanese translation
|
||||
|
||||
## [1.5.1] - 2016-05-17
|
||||
|
||||
- Fix build on F-Droid
|
||||
|
||||
## [1.5.0] - 2016-05-15
|
||||
|
||||
- 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] - 2016-04-09
|
||||
|
||||
- Show error message on widgets, instead of crashing
|
||||
- Complete French translation
|
||||
- Minor fixes to other translations
|
||||
|
||||
## [1.4.0] - 2016-04-07
|
||||
|
||||
- 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] - 2016-03-20
|
||||
|
||||
- Add Spanish and Korean translations
|
||||
- Make small corrections to other translations
|
||||
- Fix incorrect date in history calendar
|
||||
|
||||
## [1.3.2] - 2016-03-18
|
||||
|
||||
- Add Arabic, Italian, Polish, Russian and Swedish translations
|
||||
- Minor fixes to German and French translations
|
||||
- Minor bug fixes
|
||||
|
||||
## [1.3.1] - 2016-03-15
|
||||
|
||||
- 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] - 2016-03-12
|
||||
|
||||
- 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] - 2016-03-04
|
||||
|
||||
- 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] - 2016-02-24
|
||||
|
||||
- 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] - 2016-02-19
|
||||
|
||||
- Initial release
|
||||
|
||||
141
NOTICE.md
@@ -59,28 +59,6 @@ under the SIL OFL 1.1.
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
|
||||
### DragSortListView
|
||||
|
||||
<https://github.com/bauerca/drag-sort-listview>
|
||||
|
||||
A subclass of the Android ListView component that enables drag
|
||||
and drop re-ordering of list items.
|
||||
|
||||
Copyright 2012 Carl Bauer
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Material Design Icons
|
||||
|
||||
<https://github.com/google/material-design-icons>
|
||||
@@ -108,3 +86,122 @@ Extended linear layout that wrap its content when there is no place in the curre
|
||||
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.
|
||||
|
||||
144
README.md
@@ -1,52 +1,26 @@
|
||||
# Loop Habit Tracker
|
||||
<a href="https://circleci.com/gh/iSoron/uhabits/tree/dev">
|
||||
<img src="https://img.shields.io/circleci/project/iSoron/uhabits/dev.svg">
|
||||
</a>
|
||||
<a href="https://codecov.io/github/iSoron/uhabits?branch=dev">
|
||||
<img src="https://img.shields.io/codecov/c/github/iSoron/uhabits.svg" alt="Coverage via Codecov" />
|
||||
</a>
|
||||
<h1 align="center">Loop Habit Tracker</h1>
|
||||
<p align="center">
|
||||
<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/latest">
|
||||
<img src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
||||
</a>
|
||||
<a href="https://github.com/iSoron/uhabits/discussions">
|
||||
<img src="https://img.shields.io/badge/GitHub-Discussions-%23fc4ebc" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Loop is a simple Android app that helps you create and maintain good habits,
|
||||
Loop is a mobile app that helps you create and maintain good habits,
|
||||
allowing you to achieve your long-term goals. Detailed graphs and statistics
|
||||
show you how your habits improved over time. It is completely ad-free and open
|
||||
source.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Git if on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
* **Simple, beautiful and modern interface.** Loop has a minimalistic interface
|
||||
that is easy to use and follows the material design guidelines.
|
||||
|
||||
* **Habit score.** In addition to showing your current streak, Loop has an
|
||||
advanced algorithm for calculating the strength of your habits. Every
|
||||
repetition makes your habit stronger, and every missed day makes it weaker. A
|
||||
few missed days after a long streak, however, will not completely destroy
|
||||
your entire progress.
|
||||
|
||||
* **Detailed graphs and statistics.** Clearly see how your habits improved over
|
||||
time with beautiful and detailed graphs. Scroll back to see the complete
|
||||
history of your habits.
|
||||
|
||||
* **Flexible schedules.** Supports both daily habits and habits with more
|
||||
complex schedules, such as 3 times every week; one time every other week; or
|
||||
every other day.
|
||||
|
||||
* **Reminders.** Create an individual reminder for each habit, at a chosen hour
|
||||
of the day. Easily check, dismiss or snooze your habit directly from the
|
||||
notification, without opening the app.
|
||||
|
||||
* **Optimized for smartwatches.** Reminders can be checked, snoozed or
|
||||
dismissed directly from your Android Wear watch.
|
||||
|
||||
* **Completely ad-free and open source.** There are absolutely no
|
||||
advertisements, annoying notifications or intrusive permissions in this app,
|
||||
and there will never be. The complete source code is available under the
|
||||
GPLv3.
|
||||
|
||||
## Screenshots
|
||||
|
||||
[![Main screen][screen1th]][screen1]
|
||||
@@ -56,12 +30,41 @@ source.
|
||||
[![Widgets][screen5th]][screen5]
|
||||
[![Night mode][screen6th]][screen6]
|
||||
|
||||
## 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 [building instructions][build].
|
||||
app from the source code, see [build instructions][build].
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -71,8 +74,10 @@ 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
|
||||
to improve it. To report a problem, please [create a new bug report](https://github.com/iSoron/uhabits/issues/new/choose).
|
||||
To request a new feature or vote on existing feature requests, please visit
|
||||
our [GitHub Discussions page](https://github.com/iSoron/uhabits/discussions/categories/feature-requests).
|
||||
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
|
||||
@@ -81,43 +86,46 @@ contribute, even if you are not a software developer.
|
||||
|
||||
* **Translate the app into your own language.** If you are not a native English
|
||||
speaker, and would like to see the app translated into your own language,
|
||||
please join our [open translation project at POEditor][poedit]. If the translation
|
||||
please join our [open translation project][poedit]. If the translation
|
||||
is already completed, you are also very welcome to join and proofread it.
|
||||
|
||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||
contribute with code. Please, see the [developer guidelines][dev-guide] for more details.
|
||||
contribute with code. Please see `docs/GUIDELINES.md`.
|
||||
|
||||
## License
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
Copyright (C) 2016-2021 Á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/original/uhabits1.png
|
||||
[screen2]: screenshots/original/uhabits2.png
|
||||
[screen3]: screenshots/original/uhabits3.png
|
||||
[screen4]: screenshots/original/uhabits4.png
|
||||
[screen5]: screenshots/original/uhabits5.png
|
||||
[screen6]: screenshots/original/uhabits6.png
|
||||
[screen1th]: screenshots/thumbs/uhabits1.png
|
||||
[screen2th]: screenshots/thumbs/uhabits2.png
|
||||
[screen3th]: screenshots/thumbs/uhabits3.png
|
||||
[screen4th]: screenshots/thumbs/uhabits4.png
|
||||
[screen5th]: screenshots/thumbs/uhabits5.png
|
||||
[screen6th]: screenshots/thumbs/uhabits6.png
|
||||
[poedit]: https://poeditor.com/join/project/8DWX5pfjS0
|
||||
[screen1]: screenshots/1.png
|
||||
[screen2]: screenshots/2.png
|
||||
[screen3]: screenshots/3.png
|
||||
[screen4]: screenshots/4.png
|
||||
[screen5]: screenshots/5.png
|
||||
[screen6]: screenshots/6.png
|
||||
[screen1th]: screenshots/1.thumb.png
|
||||
[screen2th]: screenshots/2.thumb.png
|
||||
[screen3th]: screenshots/3.thumb.png
|
||||
[screen4th]: screenshots/4.thumb.png
|
||||
[screen5th]: screenshots/5.thumb.png
|
||||
[screen6th]: screenshots/6.thumb.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
|
||||
[build]: https://github.com/iSoron/uhabits/blob/dev/docs/BUILD.md
|
||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.isoron.uhabits"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 23
|
||||
|
||||
buildConfigField "Integer", "databaseVersion", "14"
|
||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
//testInstrumentationRunnerArgument "size", "small"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
debug {
|
||||
testCoverageEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:23.3.0'
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile 'com.android.support:design:23.3.0'
|
||||
compile 'com.android.support:preference-v14:23.3.0'
|
||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||
compile 'org.apmem.tools:layouts:1.10@aar'
|
||||
compile 'com.opencsv:opencsv:3.7'
|
||||
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
|
||||
|
||||
compile project(':libs:drag-sort-listview:library')
|
||||
|
||||
androidTestCompile 'com.android.support:support-annotations:23.3.0'
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') {
|
||||
commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ')
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.startsWith('connected')) {
|
||||
task.dependsOn grantAnimationPermission
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
</lint>
|
||||
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
P=/sdcard/Android/data/org.isoron.uhabits/cache/Failed/
|
||||
|
||||
adb pull $P Failed/
|
||||
adb shell rm -r $P
|
||||
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class BaseTest
|
||||
{
|
||||
protected Context testContext;
|
||||
protected Context targetContext;
|
||||
private static boolean isLooperPrepared;
|
||||
|
||||
public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC)
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
if(!isLooperPrepared)
|
||||
{
|
||||
Looper.prepare();
|
||||
isLooperPrepared = true;
|
||||
}
|
||||
|
||||
targetContext = InstrumentationRegistry.getTargetContext();
|
||||
testContext = InstrumentationRegistry.getContext();
|
||||
|
||||
UIHelper.setFixedTheme(R.style.AppBaseTheme);
|
||||
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
|
||||
}
|
||||
|
||||
protected void waitForAsyncTasks() throws InterruptedException, TimeoutException
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
BaseTask.waitForTasks(10000);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.preference.Preference;
|
||||
import android.view.View;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class HabitMatchers
|
||||
{
|
||||
public static Matcher<Habit> withName(final String name)
|
||||
{
|
||||
return new TypeSafeMatcher<Habit>()
|
||||
{
|
||||
@Override
|
||||
public boolean matchesSafely(Habit habit)
|
||||
{
|
||||
return habit.name.equals(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description)
|
||||
{
|
||||
description.appendText("name should be ").appendText(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatchSafely(Habit habit, Description description)
|
||||
{
|
||||
description.appendText("was ").appendText(habit.name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> containsHabit(final Matcher<Habit> matcher)
|
||||
{
|
||||
return new TypeSafeMatcher<View>()
|
||||
{
|
||||
@Override
|
||||
protected boolean matchesSafely(View view)
|
||||
{
|
||||
Adapter adapter = ((AdapterView) view).getAdapter();
|
||||
for (int i = 0; i < adapter.getCount(); i++)
|
||||
if (matcher.matches(adapter.getItem(i))) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description)
|
||||
{
|
||||
description.appendText("with class name: ");
|
||||
matcher.describeTo(description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<?> isPreferenceWithText(final String text)
|
||||
{
|
||||
return (Matcher<?>) new BaseMatcher()
|
||||
{
|
||||
@Override
|
||||
public boolean matches(Object o)
|
||||
{
|
||||
if(!(o instanceof Preference)) return false;
|
||||
return o.toString().contains(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description)
|
||||
{
|
||||
description.appendText(String.format("is preference with text '%s'", text));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.support.test.espresso.UiController;
|
||||
import android.support.test.espresso.ViewAction;
|
||||
import android.support.test.espresso.action.CoordinatesProvider;
|
||||
import android.support.test.espresso.action.GeneralClickAction;
|
||||
import android.support.test.espresso.action.GeneralLocation;
|
||||
import android.support.test.espresso.action.Press;
|
||||
import android.support.test.espresso.action.Tap;
|
||||
import android.support.test.espresso.matcher.ViewMatchers;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitViewActions
|
||||
{
|
||||
public static ViewAction toggleAllCheckmarks()
|
||||
{
|
||||
final GeneralClickAction clickAction =
|
||||
new GeneralClickAction(Tap.LONG, GeneralLocation.CENTER, Press.FINGER);
|
||||
|
||||
return new ViewAction()
|
||||
{
|
||||
@Override
|
||||
public Matcher<View> getConstraints()
|
||||
{
|
||||
return ViewMatchers.isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return "toggleAllCheckmarks";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, View view)
|
||||
{
|
||||
if (view.getId() != R.id.llButtons)
|
||||
throw new InvalidParameterException("View must have id llButtons");
|
||||
|
||||
LinearLayout llButtons = (LinearLayout) view;
|
||||
int count = llButtons.getChildCount();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
TextView tvButton = (TextView) llButtons.getChildAt(i);
|
||||
clickAction.perform(uiController, tvButton);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ViewAction clickAt(final int x, final int y)
|
||||
{
|
||||
return new GeneralClickAction(Tap.SINGLE, new CoordinatesProvider()
|
||||
{
|
||||
@Override
|
||||
public float[] calculateCoordinates(View view)
|
||||
{
|
||||
int[] locations = new int[2];
|
||||
view.getLocationOnScreen(locations);
|
||||
|
||||
final float locationX = locations[0] + x;
|
||||
final float locationY = locations[1] + y;
|
||||
|
||||
return new float[]{locationX, locationY};
|
||||
}
|
||||
}, Press.FINGER);
|
||||
}
|
||||
|
||||
public static ViewAction clickAtRandomLocations(final int count)
|
||||
{
|
||||
return new ViewAction()
|
||||
{
|
||||
@Override
|
||||
public Matcher<View> getConstraints()
|
||||
{
|
||||
return ViewMatchers.isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return "clickAtRandomLocations";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, View view)
|
||||
{
|
||||
int width = view.getWidth();
|
||||
int height = view.getHeight();
|
||||
Random random = new Random();
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
int x = random.nextInt(width);
|
||||
int y = random.nextInt(height);
|
||||
|
||||
ViewAction action = clickAt(x, y);
|
||||
action.perform(uiController, view);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.support.test.espresso.NoMatchingViewException;
|
||||
import android.support.test.espresso.contrib.RecyclerViewActions;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onData;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.Espresso.pressBack;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.longClick;
|
||||
import static android.support.test.espresso.action.ViewActions.replaceText;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.isoron.uhabits.ui.HabitMatchers.containsHabit;
|
||||
import static org.isoron.uhabits.ui.HabitMatchers.withName;
|
||||
|
||||
public class MainActivityActions
|
||||
{
|
||||
public static String addHabit()
|
||||
{
|
||||
return addHabit(false);
|
||||
}
|
||||
|
||||
public static String addHabit(boolean openDialogs)
|
||||
{
|
||||
String name = "New Habit " + new Random().nextInt(1000000);
|
||||
String description = "Did you perform your new habit today?";
|
||||
String num = "4";
|
||||
String den = "8";
|
||||
|
||||
onView(withId(R.id.action_add))
|
||||
.perform(click());
|
||||
|
||||
typeHabitData(name, description, num, den);
|
||||
|
||||
if(openDialogs)
|
||||
{
|
||||
onView(withId(R.id.buttonPickColor))
|
||||
.perform(click());
|
||||
pressBack();
|
||||
onView(withId(R.id.inputReminderTime))
|
||||
.perform(click());
|
||||
onView(withText("Done"))
|
||||
.perform(click());
|
||||
onView(withId(R.id.inputReminderDays))
|
||||
.perform(click());
|
||||
onView(withText("OK"))
|
||||
.perform(click());
|
||||
}
|
||||
|
||||
onView(withId(R.id.buttonSave))
|
||||
.perform(click());
|
||||
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.label));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static void typeHabitData(String name, String description, String num, String den)
|
||||
{
|
||||
onView(withId(R.id.input_name))
|
||||
.perform(replaceText(name));
|
||||
onView(withId(R.id.input_description))
|
||||
.perform(replaceText(description));
|
||||
|
||||
try
|
||||
{
|
||||
onView(allOf(withId(R.id.sFrequency), withEffectiveVisibility(VISIBLE)))
|
||||
.perform(click());
|
||||
onData(allOf(instanceOf(String.class), startsWith("Custom")))
|
||||
.inRoot(isPlatformPopup())
|
||||
.perform(click());
|
||||
}
|
||||
catch(NoMatchingViewException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
onView(withId(R.id.input_freq_num))
|
||||
.perform(replaceText(num));
|
||||
onView(withId(R.id.input_freq_den))
|
||||
.perform(replaceText(den));
|
||||
}
|
||||
|
||||
public static void selectHabit(String name)
|
||||
{
|
||||
selectHabits(Collections.singletonList(name));
|
||||
}
|
||||
|
||||
public static void selectHabits(List<String> names)
|
||||
{
|
||||
boolean first = true;
|
||||
for(String name : names)
|
||||
{
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.label))
|
||||
.perform(first ? longClick() : click());
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertHabitsDontExist(List<String> names)
|
||||
{
|
||||
for(String name : names)
|
||||
onView(withId(R.id.listView))
|
||||
.check(matches(not(containsHabit(withName(name)))));
|
||||
}
|
||||
|
||||
public static void assertHabitExists(String name)
|
||||
{
|
||||
List<String> names = new LinkedList<>();
|
||||
names.add(name);
|
||||
assertHabitsExist(names);
|
||||
}
|
||||
|
||||
public static void assertHabitsExist(List<String> names)
|
||||
{
|
||||
for(String name : names)
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
public static void deleteHabit(String name)
|
||||
{
|
||||
deleteHabits(Collections.singletonList(name));
|
||||
}
|
||||
|
||||
public static void deleteHabits(List<String> names)
|
||||
{
|
||||
selectHabits(names);
|
||||
clickMenuItem(R.string.delete);
|
||||
onView(withText("OK"))
|
||||
.perform(click());
|
||||
assertHabitsDontExist(names);
|
||||
}
|
||||
|
||||
public static void clickMenuItem(int stringId)
|
||||
{
|
||||
try
|
||||
{
|
||||
onView(withText(stringId)).perform(click());
|
||||
}
|
||||
catch (Exception e1)
|
||||
{
|
||||
try
|
||||
{
|
||||
onView(withContentDescription(stringId)).perform(click());
|
||||
}
|
||||
catch(Exception e2)
|
||||
{
|
||||
clickHiddenMenuItem(stringId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void clickHiddenMenuItem(int stringId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try the ActionMode overflow menu first
|
||||
onView(allOf(withContentDescription("More options"), withParent(withParent(
|
||||
withClassName(containsString("Action")))))).perform(click());
|
||||
}
|
||||
catch (Exception e1)
|
||||
{
|
||||
// Try the toolbar overflow menu
|
||||
onView(allOf(withContentDescription("More options"), withParent(withParent(
|
||||
withClassName(containsString("Toolbar")))))).perform(click());
|
||||
}
|
||||
|
||||
onView(withText(stringId)).perform(click());
|
||||
}
|
||||
|
||||
public static void clickSettingsItem(String text)
|
||||
{
|
||||
onView(withClassName(containsString("RecyclerView")))
|
||||
.perform(RecyclerViewActions.actionOnItem(
|
||||
hasDescendant(withText(containsString(text))),
|
||||
click()));
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.espresso.NoMatchingViewException;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.isoron.uhabits.MainActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onData;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.Espresso.pressBack;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.longClick;
|
||||
import static android.support.test.espresso.action.ViewActions.scrollTo;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeLeft;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeRight;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeUp;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.intent.Intents.intended;
|
||||
import static android.support.test.espresso.intent.Intents.intending;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.isoron.uhabits.ui.HabitMatchers.withName;
|
||||
import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations;
|
||||
import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.addHabit;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.clickMenuItem;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.clickSettingsItem;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.selectHabits;
|
||||
import static org.isoron.uhabits.ui.MainActivityActions.typeHabitData;
|
||||
import static org.isoron.uhabits.ui.ShowHabitActivityActions.openHistoryEditor;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class MainTest
|
||||
{
|
||||
private SystemHelper sys;
|
||||
|
||||
@Rule
|
||||
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(
|
||||
MainActivity.class);
|
||||
|
||||
private Context targetContext;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
sys = new SystemHelper(context);
|
||||
sys.disableAllAnimations();
|
||||
sys.acquireWakeLock();
|
||||
sys.unlockScreen();
|
||||
|
||||
targetContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
Instrumentation.ActivityResult okResult = new Instrumentation.ActivityResult(
|
||||
Activity.RESULT_OK, new Intent());
|
||||
|
||||
intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult);
|
||||
intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(okResult);
|
||||
intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult);
|
||||
|
||||
skipTutorial();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
{
|
||||
sys.releaseWakeLock();
|
||||
}
|
||||
|
||||
public void skipTutorial()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
onView(allOf(withClassName(endsWith("AppCompatImageButton")),
|
||||
isDisplayed())).perform(click());
|
||||
}
|
||||
catch (NoMatchingViewException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens the app, creates some habits, selects them, archives them, select 'show archived'
|
||||
* on the menu, selects the previously archived habits and then deletes them.
|
||||
*/
|
||||
@Test
|
||||
public void testArchiveHabits()
|
||||
{
|
||||
List<String> names = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
names.add(addHabit());
|
||||
|
||||
selectHabits(names);
|
||||
|
||||
clickMenuItem(R.string.archive);
|
||||
assertHabitsDontExist(names);
|
||||
|
||||
clickMenuItem(R.string.show_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
selectHabits(names);
|
||||
clickMenuItem(R.string.unarchive);
|
||||
|
||||
clickMenuItem(R.string.show_archived);
|
||||
|
||||
assertHabitsExist(names);
|
||||
deleteHabits(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens the app, clicks the add button, types some bogus information, tries to save,
|
||||
* dialog displays an error.
|
||||
*/
|
||||
@Test
|
||||
public void testAddInvalidHabit()
|
||||
{
|
||||
onView(withId(R.id.action_add))
|
||||
.perform(click());
|
||||
|
||||
typeHabitData("", "", "15", "7");
|
||||
|
||||
onView(withId(R.id.buttonSave)).perform(click());
|
||||
onView(withId(R.id.input_name)).check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
/**
|
||||
* User creates a habit, toggles a bunch of checkmarks, clicks the habit to open the statistics
|
||||
* screen, scrolls down to some views, then scrolls the views backwards and forwards in time.
|
||||
*/
|
||||
@Test
|
||||
public void testAddHabitAndViewStats() throws InterruptedException
|
||||
{
|
||||
String name = addHabit(true);
|
||||
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.llButtons))
|
||||
.perform(toggleAllCheckmarks());
|
||||
|
||||
Thread.sleep(1200);
|
||||
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.label))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.scoreView))
|
||||
.perform(scrollTo(), swipeRight());
|
||||
|
||||
onView(withId(R.id.punchcardView))
|
||||
.perform(scrollTo(), swipeRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* User creates a habit, selects the habit, clicks edit button, changes some information about
|
||||
* the habit, click save button, sees changes on the main window, selects habit again,
|
||||
* changes color, then deletes the habit.
|
||||
*/
|
||||
@Test
|
||||
public void testEditHabit()
|
||||
{
|
||||
String name = addHabit();
|
||||
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.label))
|
||||
.perform(longClick());
|
||||
|
||||
clickMenuItem(R.string.edit);
|
||||
|
||||
String modifiedName = "Modified " + new Random().nextInt(10000);
|
||||
typeHabitData(modifiedName, "", "1", "1");
|
||||
|
||||
onView(withId(R.id.buttonSave))
|
||||
.perform(click());
|
||||
|
||||
assertHabitExists(modifiedName);
|
||||
|
||||
selectHabit(modifiedName);
|
||||
clickMenuItem(R.string.color_picker_default_title);
|
||||
pressBack();
|
||||
|
||||
deleteHabit(modifiedName);
|
||||
}
|
||||
|
||||
/**
|
||||
* User creates a habit, opens statistics page, clicks button to edit history, adds some
|
||||
* checkmarks, closes dialog, sees the modified history calendar.
|
||||
*/
|
||||
@Test
|
||||
public void testEditHistory()
|
||||
{
|
||||
String name = addHabit();
|
||||
|
||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
||||
.onChildView(withId(R.id.label))
|
||||
.perform(click());
|
||||
|
||||
openHistoryEditor();
|
||||
onView(withClassName(endsWith("HabitHistoryView")))
|
||||
.perform(clickAtRandomLocations(20));
|
||||
|
||||
pressBack();
|
||||
onView(withId(R.id.historyView))
|
||||
.perform(scrollTo(), swipeRight(), swipeLeft());
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens menu, clicks settings, sees settings screen.
|
||||
*/
|
||||
@Test
|
||||
public void testSettings()
|
||||
{
|
||||
clickMenuItem(R.string.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens menu, clicks about, sees about screen.
|
||||
*/
|
||||
@Test
|
||||
public void testAbout()
|
||||
{
|
||||
clickMenuItem(R.string.about);
|
||||
onView(isRoot()).perform(swipeUp());
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens menu, clicks Help, sees website.
|
||||
*/
|
||||
@Test
|
||||
public void testHelp()
|
||||
{
|
||||
clickMenuItem(R.string.help);
|
||||
intended(hasAction(Intent.ACTION_VIEW));
|
||||
}
|
||||
|
||||
/**
|
||||
* User creates a habit, exports full backup, deletes the habit, restores backup, sees that the
|
||||
* previously created habit has appeared back.
|
||||
*/
|
||||
@Test
|
||||
public void testExportImportDB()
|
||||
{
|
||||
String name = addHabit();
|
||||
|
||||
clickMenuItem(R.string.settings);
|
||||
|
||||
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
|
||||
date = date.substring(0, date.length() - 2);
|
||||
|
||||
clickSettingsItem("Export full backup");
|
||||
intended(hasAction(Intent.ACTION_SEND));
|
||||
|
||||
deleteHabit(name);
|
||||
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Import data");
|
||||
|
||||
onData(allOf(is(instanceOf(String.class)), startsWith("Backups")))
|
||||
.perform(click());
|
||||
|
||||
onData(allOf(is(instanceOf(String.class)), containsString(date)))
|
||||
.perform(click());
|
||||
|
||||
selectHabit(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* User creates a habit, opens settings, clicks export as CSV, is asked what activity should
|
||||
* handle the file.
|
||||
*/
|
||||
@Test
|
||||
public void testExportCSV()
|
||||
{
|
||||
addHabit();
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Export as CSV");
|
||||
intended(hasAction(Intent.ACTION_SEND));
|
||||
}
|
||||
|
||||
/**
|
||||
* User opens the settings and generates a bug report.
|
||||
*/
|
||||
@Test
|
||||
public void testGenerateBugReport()
|
||||
{
|
||||
clickMenuItem(R.string.settings);
|
||||
clickSettingsItem("Generate bug report");
|
||||
intended(hasAction(Intent.ACTION_SENDTO));
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package org.isoron.uhabits.ui;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.support.test.runner.AndroidJUnitRunner;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class SystemHelper extends AndroidJUnitRunner
|
||||
{
|
||||
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
|
||||
private static final float DISABLED = 0.0f;
|
||||
private static final float DEFAULT = 1.0f;
|
||||
|
||||
private final Context context;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
||||
SystemHelper(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void acquireWakeLock()
|
||||
{
|
||||
PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
wakeLock = power.newWakeLock(PowerManager.FULL_WAKE_LOCK |
|
||||
PowerManager.ACQUIRE_CAUSES_WAKEUP |
|
||||
PowerManager.ON_AFTER_RELEASE, getClass().getSimpleName());
|
||||
wakeLock.acquire();
|
||||
}
|
||||
|
||||
void releaseWakeLock()
|
||||
{
|
||||
if(wakeLock != null)
|
||||
wakeLock.release();
|
||||
}
|
||||
|
||||
void unlockScreen()
|
||||
{
|
||||
Log.i("SystemHelper", "Trying to unlock screen");
|
||||
try
|
||||
{
|
||||
KeyguardManager mKeyGuardManager =
|
||||
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
||||
KeyguardManager.KeyguardLock mLock = mKeyGuardManager.newKeyguardLock("lock");
|
||||
mLock.disableKeyguard();
|
||||
Log.e("SystemHelper", "Successfully unlocked screen");
|
||||
} catch (Exception e)
|
||||
{
|
||||
Log.e("SystemHelper", "Could not unlock screen");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void disableAllAnimations()
|
||||
{
|
||||
Log.i("SystemHelper", "Trying to disable animations");
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED) setSystemAnimationsScale(DISABLED);
|
||||
else Log.e("SystemHelper", "Permission denied");
|
||||
|
||||
}
|
||||
|
||||
void enableAllAnimations()
|
||||
{
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
setSystemAnimationsScale(DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSystemAnimationsScale(float animationScale)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
|
||||
Method asInterface =
|
||||
windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
|
||||
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
|
||||
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
|
||||
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
|
||||
Method setAnimationScales =
|
||||
windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
|
||||
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
|
||||
|
||||
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
|
||||
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
|
||||
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
|
||||
for (int i = 0; i < currentScales.length; i++)
|
||||
currentScales[i] = animationScale;
|
||||
|
||||
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
|
||||
Log.i("SystemHelper", "All animations successfully disabled");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("SystemHelper", "Could not change animation scale to " + animationScale + " :'(");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class HabitFixtures
|
||||
{
|
||||
public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false,
|
||||
false, true, true };
|
||||
|
||||
public static Habit createShortHabit()
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.name = "Wake up early";
|
||||
habit.description = "Did you wake up before 6am?";
|
||||
habit.freqNum = 2;
|
||||
habit.freqDen = 3;
|
||||
habit.save();
|
||||
|
||||
long timestamp = DateHelper.getStartOfToday();
|
||||
for(boolean c : NON_DAILY_HABIT_CHECKS)
|
||||
{
|
||||
if(c) habit.repetitions.toggle(timestamp);
|
||||
timestamp -= DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public static Habit createEmptyHabit()
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.name = "Meditate";
|
||||
habit.description = "Did you meditate this morning?";
|
||||
habit.color = 3;
|
||||
habit.freqNum = 1;
|
||||
habit.freqDen = 1;
|
||||
habit.save();
|
||||
return habit;
|
||||
}
|
||||
|
||||
public static Habit createLongHabit()
|
||||
{
|
||||
Habit habit = createEmptyHabit();
|
||||
habit.freqNum = 3;
|
||||
habit.freqDen = 7;
|
||||
habit.color = 4;
|
||||
habit.save();
|
||||
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52,
|
||||
53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95,
|
||||
102, 103, 108, 109, 120};
|
||||
|
||||
for(int mark : marks)
|
||||
habit.repetitions.toggle(today - mark * day);
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public static void generateHugeDataSet() throws Throwable
|
||||
{
|
||||
final int nHabits = 30;
|
||||
final int nYears = 5;
|
||||
|
||||
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
|
||||
{
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Random rand = new Random();
|
||||
|
||||
for(int i = 0; i < nHabits; i++)
|
||||
{
|
||||
Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits));
|
||||
|
||||
Habit habit = new Habit();
|
||||
habit.name = String.format("Habit %d", i);
|
||||
habit.save();
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
|
||||
for(int j = 0; j < 365 * nYears; j++)
|
||||
{
|
||||
if(rand.nextBoolean())
|
||||
habit.repetitions.toggle(today - j * day);
|
||||
}
|
||||
|
||||
habit.scores.getTodayValue();
|
||||
habit.streaks.getAll(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ExportDBTask task = new ExportDBTask(null);
|
||||
task.setListener(new ExportDBTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onExportDBFinished(@Nullable String filename)
|
||||
{
|
||||
if(filename != null)
|
||||
Log.i("HabitFixture", String.format("Huge data set exported to %s", filename));
|
||||
else
|
||||
Log.i("HabitFixture", "Failed to save database");
|
||||
}
|
||||
});
|
||||
task.execute();
|
||||
|
||||
BaseTask.waitForTasks(30000);
|
||||
}
|
||||
|
||||
public static void loadHugeDataSet(Context testContext) throws Throwable
|
||||
{
|
||||
File baseDir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(baseDir == null) fail("baseDir should not be null");
|
||||
|
||||
File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db"));
|
||||
InputStream in = testContext.getAssets().open("fixtures/loopHuge.db");
|
||||
DatabaseHelper.copy(in, dst);
|
||||
|
||||
ImportDataTask task = new ImportDataTask(dst, null);
|
||||
task.execute();
|
||||
|
||||
BaseTask.waitForTasks(30000);
|
||||
}
|
||||
|
||||
public static void purgeHabits()
|
||||
{
|
||||
for(Habit h : Habit.getAll(true))
|
||||
h.cascadeDelete();
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitsApplicationTest
|
||||
{
|
||||
@Test
|
||||
public void test_getLogcat() throws IOException
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||
return;
|
||||
|
||||
String msg = "LOGCAT TEST";
|
||||
new RuntimeException(msg).printStackTrace();
|
||||
|
||||
String log = HabitsApplication.getLogcat();
|
||||
assertThat(log, containsString(msg));
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ArchiveHabitsCommandTest extends BaseTest
|
||||
{
|
||||
|
||||
private ArchiveHabitsCommand command;
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
command = new ArchiveHabitsCommand(Collections.singletonList(habit));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
assertFalse(habit.isArchived());
|
||||
|
||||
command.execute();
|
||||
assertTrue(habit.isArchived());
|
||||
|
||||
command.undo();
|
||||
assertFalse(habit.isArchived());
|
||||
|
||||
command.execute();
|
||||
assertTrue(habit.isArchived());
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ChangeHabitColorCommandTest extends BaseTest
|
||||
{
|
||||
private ChangeHabitColorCommand command;
|
||||
private LinkedList<Habit> habits;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
habits = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 3; i ++)
|
||||
{
|
||||
Habit habit = HabitFixtures.createShortHabit();
|
||||
habit.color = i+1;
|
||||
habit.save();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
command = new ChangeHabitColorCommand(habits, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
checkOriginalColors();
|
||||
|
||||
command.execute();
|
||||
checkNewColors();
|
||||
|
||||
command.undo();
|
||||
checkOriginalColors();
|
||||
|
||||
command.execute();
|
||||
checkNewColors();
|
||||
}
|
||||
|
||||
private void checkOriginalColors()
|
||||
{
|
||||
int k = 0;
|
||||
for(Habit h : habits)
|
||||
assertThat(h.color, equalTo(++k));
|
||||
}
|
||||
|
||||
private void checkNewColors()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
assertThat(h.color, equalTo(0));
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CreateHabitCommandTest extends BaseTest
|
||||
{
|
||||
|
||||
private CreateHabitCommand command;
|
||||
private Habit model;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
model = new Habit();
|
||||
model.name = "New habit";
|
||||
command = new CreateHabitCommand(model);
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
assertTrue(Habit.getAll(true).isEmpty());
|
||||
|
||||
command.execute();
|
||||
|
||||
List<Habit> allHabits = Habit.getAll(true);
|
||||
assertThat(allHabits.size(), equalTo(1));
|
||||
|
||||
Habit habit = allHabits.get(0);
|
||||
Long id = habit.getId();
|
||||
assertThat(habit.name, equalTo(model.name));
|
||||
|
||||
command.undo();
|
||||
assertTrue(Habit.getAll(true).isEmpty());
|
||||
|
||||
command.execute();
|
||||
allHabits = Habit.getAll(true);
|
||||
assertThat(allHabits.size(), equalTo(1));
|
||||
|
||||
habit = allHabits.get(0);
|
||||
Long newId = habit.getId();
|
||||
assertThat(id, equalTo(newId));
|
||||
assertThat(habit.name, equalTo(model.name));
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DeleteHabitsCommandTest extends BaseTest
|
||||
{
|
||||
private DeleteHabitsCommand command;
|
||||
private LinkedList<Habit> habits;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
habits = new LinkedList<>();
|
||||
|
||||
// Habits that shuold be deleted
|
||||
for(int i = 0; i < 3; i ++)
|
||||
{
|
||||
Habit habit = HabitFixtures.createShortHabit();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
// Extra habit that should not be deleted
|
||||
Habit extraHabit = HabitFixtures.createShortHabit();
|
||||
extraHabit.name = "extra";
|
||||
extraHabit.save();
|
||||
|
||||
command = new DeleteHabitsCommand(habits);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
assertThat(Habit.getAll(true).size(), equalTo(4));
|
||||
|
||||
command.execute();
|
||||
assertThat(Habit.getAll(true).size(), equalTo(1));
|
||||
assertThat(Habit.getAll(true).get(0).name, equalTo("extra"));
|
||||
|
||||
thrown.expect(UnsupportedOperationException.class);
|
||||
command.undo();
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class EditHabitCommandTest extends BaseTest
|
||||
{
|
||||
|
||||
private EditHabitCommand command;
|
||||
private Habit habit;
|
||||
private Habit modified;
|
||||
private Long id;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
habit.name = "original";
|
||||
habit.freqDen = 1;
|
||||
habit.freqNum = 1;
|
||||
habit.save();
|
||||
|
||||
id = habit.getId();
|
||||
|
||||
modified = new Habit(habit);
|
||||
modified.name = "modified";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
command = new EditHabitCommand(habit, modified);
|
||||
|
||||
int originalScore = habit.scores.getTodayValue();
|
||||
assertThat(habit.name, equalTo("original"));
|
||||
|
||||
command.execute();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("modified"));
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
||||
|
||||
command.undo();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("original"));
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
||||
|
||||
command.execute();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("modified"));
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo_withModifiedInterval()
|
||||
{
|
||||
modified.freqNum = 1;
|
||||
modified.freqDen = 7;
|
||||
command = new EditHabitCommand(habit, modified);
|
||||
|
||||
int originalScore = habit.scores.getTodayValue();
|
||||
assertThat(habit.name, equalTo("original"));
|
||||
|
||||
command.execute();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("modified"));
|
||||
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
|
||||
|
||||
command.undo();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("original"));
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
||||
|
||||
command.execute();
|
||||
refreshHabit();
|
||||
assertThat(habit.name, equalTo("modified"));
|
||||
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
|
||||
}
|
||||
|
||||
private void refreshHabit()
|
||||
{
|
||||
habit = Habit.get(id);
|
||||
assertTrue(habit != null);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ToggleRepetitionCommandTest extends BaseTest
|
||||
{
|
||||
|
||||
private ToggleRepetitionCommand command;
|
||||
private Habit habit;
|
||||
private long today;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
|
||||
today = DateHelper.getStartOfToday();
|
||||
command = new ToggleRepetitionCommand(habit, today);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
assertTrue(habit.repetitions.contains(today));
|
||||
|
||||
command.execute();
|
||||
assertFalse(habit.repetitions.contains(today));
|
||||
|
||||
command.undo();
|
||||
assertTrue(habit.repetitions.contains(today));
|
||||
|
||||
command.execute();
|
||||
assertFalse(habit.repetitions.contains(today));
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.commands;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class UnarchiveHabitsCommandTest extends BaseTest
|
||||
{
|
||||
|
||||
private UnarchiveHabitsCommand command;
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
Habit.archive(Collections.singletonList(habit));
|
||||
|
||||
command = new UnarchiveHabitsCommand(Collections.singletonList(habit));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteUndoRedo()
|
||||
{
|
||||
assertTrue(habit.isArchived());
|
||||
|
||||
command.execute();
|
||||
assertFalse(habit.isArchived());
|
||||
|
||||
command.undo();
|
||||
assertTrue(habit.isArchived());
|
||||
|
||||
command.execute();
|
||||
assertFalse(habit.isArchived());
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.io;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.io.HabitsCSVExporter;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitsCSVExporterTest extends BaseTest
|
||||
{
|
||||
private File baseDir;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
HabitFixtures.createShortHabit();
|
||||
HabitFixtures.createEmptyHabit();
|
||||
|
||||
Context targetContext = InstrumentationRegistry.getTargetContext();
|
||||
baseDir = targetContext.getCacheDir();
|
||||
}
|
||||
|
||||
private void unzip(File file) throws IOException
|
||||
{
|
||||
ZipFile zip = new ZipFile(file);
|
||||
Enumeration<? extends ZipEntry> e = zip.entries();
|
||||
|
||||
while(e.hasMoreElements())
|
||||
{
|
||||
ZipEntry entry = e.nextElement();
|
||||
InputStream stream = zip.getInputStream(entry);
|
||||
|
||||
String outputFilename = String.format("%s/%s", baseDir.getAbsolutePath(),
|
||||
entry.getName());
|
||||
File outputFile = new File(outputFilename);
|
||||
|
||||
File parent = outputFile.getParentFile();
|
||||
if(parent != null) parent.mkdirs();
|
||||
|
||||
DatabaseHelper.copy(stream, outputFile);
|
||||
}
|
||||
|
||||
zip.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportCSV() throws IOException
|
||||
{
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
|
||||
HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir);
|
||||
String filename = exporter.writeArchive();
|
||||
assertAbsolutePathExists(filename);
|
||||
|
||||
File archive = new File(filename);
|
||||
unzip(archive);
|
||||
|
||||
assertPathExists("Habits.csv");
|
||||
assertPathExists("001 Wake up early");
|
||||
assertPathExists("001 Wake up early/Checkmarks.csv");
|
||||
assertPathExists("001 Wake up early/Scores.csv");
|
||||
assertPathExists("002 Meditate/Checkmarks.csv");
|
||||
assertPathExists("002 Meditate/Scores.csv");
|
||||
}
|
||||
|
||||
private void assertPathExists(String s)
|
||||
{
|
||||
assertAbsolutePathExists(String.format("%s/%s", baseDir.getAbsolutePath(), s));
|
||||
}
|
||||
|
||||
private void assertAbsolutePathExists(String s)
|
||||
{
|
||||
File file = new File(s);
|
||||
assertTrue(String.format("File %s should exist", file.getAbsolutePath()), file.exists());
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.io;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.io.GenericImporter;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ImportTest extends BaseTest
|
||||
{
|
||||
private File baseDir;
|
||||
private Context context;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
DateHelper.setFixedLocalTime(null);
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
baseDir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(baseDir == null) fail("baseDir should not be null");
|
||||
}
|
||||
|
||||
private void copyAssetToFile(String assetPath, File dst) throws IOException
|
||||
{
|
||||
InputStream in = context.getAssets().open(assetPath);
|
||||
DatabaseHelper.copy(in, dst);
|
||||
}
|
||||
|
||||
private void importFromFile(String assetFilename) throws IOException
|
||||
{
|
||||
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
|
||||
copyAssetToFile(assetFilename, file);
|
||||
assertTrue(file.exists());
|
||||
assertTrue(file.canRead());
|
||||
|
||||
GenericImporter importer = new GenericImporter();
|
||||
assertThat(importer.canHandle(file), is(true));
|
||||
|
||||
importer.importHabitsFromFile(file);
|
||||
}
|
||||
|
||||
private boolean containsRepetition(Habit h, int year, int month, int day)
|
||||
{
|
||||
GregorianCalendar date = DateHelper.getStartOfTodayCalendar();
|
||||
date.set(year, month - 1, day);
|
||||
return h.repetitions.contains(date.getTimeInMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTickmateDB() throws IOException
|
||||
{
|
||||
importFromFile("tickmate.db");
|
||||
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
assertThat(habits.size(), equalTo(3));
|
||||
|
||||
Habit h = habits.get(0);
|
||||
assertThat(h.name, equalTo("Vegan"));
|
||||
assertTrue(containsRepetition(h, 2016, 1, 24));
|
||||
assertTrue(containsRepetition(h, 2016, 2, 5));
|
||||
assertTrue(containsRepetition(h, 2016, 3, 18));
|
||||
assertFalse(containsRepetition(h, 2016, 3, 14));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewireDB() throws IOException
|
||||
{
|
||||
importFromFile("rewire.db");
|
||||
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
assertThat(habits.size(), equalTo(3));
|
||||
|
||||
Habit habit = habits.get(0);
|
||||
assertThat(habit.name, equalTo("Wake up early"));
|
||||
assertThat(habit.freqNum, equalTo(3));
|
||||
assertThat(habit.freqDen, equalTo(7));
|
||||
assertFalse(habit.hasReminder());
|
||||
assertFalse(containsRepetition(habit, 2015, 12, 31));
|
||||
assertTrue(containsRepetition(habit, 2016, 1, 18));
|
||||
assertTrue(containsRepetition(habit, 2016, 1, 28));
|
||||
assertFalse(containsRepetition(habit, 2016, 3, 10));
|
||||
|
||||
habit = habits.get(1);
|
||||
assertThat(habit.name, equalTo("brush teeth"));
|
||||
assertThat(habit.freqNum, equalTo(3));
|
||||
assertThat(habit.freqDen, equalTo(7));
|
||||
assertThat(habit.reminderHour, equalTo(8));
|
||||
assertThat(habit.reminderMin, equalTo(0));
|
||||
boolean[] reminderDays = {false, true, true, true, true, true, false};
|
||||
assertThat(habit.reminderDays, equalTo(DateHelper.packWeekdayList(reminderDays)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHabitBullCSV() throws IOException
|
||||
{
|
||||
importFromFile("habitbull.csv");
|
||||
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
assertThat(habits.size(), equalTo(4));
|
||||
|
||||
Habit habit = habits.get(0);
|
||||
assertThat(habit.name, equalTo("Breed dragons"));
|
||||
assertThat(habit.description, equalTo("with love and fire"));
|
||||
assertThat(habit.freqNum, equalTo(1));
|
||||
assertThat(habit.freqDen, equalTo(1));
|
||||
assertTrue(containsRepetition(habit, 2016, 3, 18));
|
||||
assertTrue(containsRepetition(habit, 2016, 3, 19));
|
||||
assertFalse(containsRepetition(habit, 2016, 3, 20));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoopDB() throws IOException
|
||||
{
|
||||
importFromFile("loop.db");
|
||||
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
assertThat(habits.size(), equalTo(9));
|
||||
|
||||
Habit habit = habits.get(0);
|
||||
assertThat(habit.name, equalTo("Wake up early"));
|
||||
assertThat(habit.freqNum, equalTo(3));
|
||||
assertThat(habit.freqDen, equalTo(7));
|
||||
assertTrue(containsRepetition(habit, 2016, 3, 14));
|
||||
assertTrue(containsRepetition(habit, 2016, 3, 16));
|
||||
assertFalse(containsRepetition(habit, 2016, 3, 17));
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
|
||||
import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY;
|
||||
import static org.isoron.uhabits.models.Checkmark.UNCHECKED;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CheckmarkListTest extends BaseTest
|
||||
{
|
||||
Habit nonDailyHabit;
|
||||
private Habit emptyHabit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
nonDailyHabit = HabitFixtures.createShortHabit();
|
||||
emptyHabit = HabitFixtures.createEmptyHabit();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
{
|
||||
DateHelper.setFixedLocalTime(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withNonDailyHabit()
|
||||
{
|
||||
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED,
|
||||
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
||||
|
||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withEmptyHabit()
|
||||
{
|
||||
int[] expectedValues = new int[0];
|
||||
int[] actualValues = emptyHabit.checkmarks.getAllValues();
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_moveForwardInTime()
|
||||
{
|
||||
travelInTime(3);
|
||||
|
||||
int[] expectedValues = { UNCHECKED, UNCHECKED, UNCHECKED, CHECKED_EXPLICITLY, UNCHECKED,
|
||||
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
|
||||
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
||||
|
||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_moveBackwardsInTime()
|
||||
{
|
||||
travelInTime(-3);
|
||||
|
||||
int[] expectedValues = { CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
|
||||
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
||||
|
||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValues_withInvalidInterval()
|
||||
{
|
||||
int values[] = nonDailyHabit.checkmarks.getValues(100L, -100L);
|
||||
assertThat(values, equalTo(new int[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValues_withValidInterval()
|
||||
{
|
||||
long from = DateHelper.getStartOfToday() - 15 * DateHelper.millisecondsInOneDay;
|
||||
long to = DateHelper.getStartOfToday() - 5 * DateHelper.millisecondsInOneDay;
|
||||
|
||||
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
|
||||
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, UNCHECKED, UNCHECKED, UNCHECKED,
|
||||
UNCHECKED, UNCHECKED };
|
||||
|
||||
int[] actualValues = nonDailyHabit.checkmarks.getValues(from, to);
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayValue()
|
||||
{
|
||||
travelInTime(-1);
|
||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||
|
||||
travelInTime(0);
|
||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
|
||||
|
||||
travelInTime(1);
|
||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
String expectedCSV =
|
||||
"2015-01-16,2\n" +
|
||||
"2015-01-17,2\n" +
|
||||
"2015-01-18,1\n" +
|
||||
"2015-01-19,0\n" +
|
||||
"2015-01-20,2\n" +
|
||||
"2015-01-21,2\n" +
|
||||
"2015-01-22,2\n" +
|
||||
"2015-01-23,1\n" +
|
||||
"2015-01-24,0\n" +
|
||||
"2015-01-25,2\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
nonDailyHabit.checkmarks.writeCSV(writer);
|
||||
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
private void travelInTime(int days)
|
||||
{
|
||||
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME +
|
||||
days * DateHelper.millisecondsInOneDay);
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitTest extends BaseTest
|
||||
{
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
HabitFixtures.purgeHabits();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor_default()
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
assertThat(habit.archived, is(0));
|
||||
assertThat(habit.highlight, is(0));
|
||||
|
||||
assertThat(habit.reminderHour, is(nullValue()));
|
||||
assertThat(habit.reminderMin, is(nullValue()));
|
||||
|
||||
assertThat(habit.reminderDays, is(not(nullValue())));
|
||||
assertThat(habit.streaks, is(not(nullValue())));
|
||||
assertThat(habit.scores, is(not(nullValue())));
|
||||
assertThat(habit.repetitions, is(not(nullValue())));
|
||||
assertThat(habit.checkmarks, is(not(nullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor_habit()
|
||||
{
|
||||
Habit model = new Habit();
|
||||
model.archived = 1;
|
||||
model.highlight = 1;
|
||||
model.color = 0;
|
||||
model.freqNum = 10;
|
||||
model.freqDen = 20;
|
||||
model.reminderDays = 1;
|
||||
model.reminderHour = 8;
|
||||
model.reminderMin = 30;
|
||||
model.position = 0;
|
||||
|
||||
Habit habit = new Habit(model);
|
||||
assertThat(habit.archived, is(model.archived));
|
||||
assertThat(habit.highlight, is(model.highlight));
|
||||
assertThat(habit.color, is(model.color));
|
||||
assertThat(habit.freqNum, is(model.freqNum));
|
||||
assertThat(habit.freqDen, is(model.freqDen));
|
||||
assertThat(habit.reminderDays, is(model.reminderDays));
|
||||
assertThat(habit.reminderHour, is(model.reminderHour));
|
||||
assertThat(habit.reminderMin, is(model.reminderMin));
|
||||
assertThat(habit.position, is(model.position));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_get_withValidId()
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.save();
|
||||
|
||||
Habit habit2 = Habit.get(habit.getId());
|
||||
assertThat(habit, equalTo(habit2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_get_withInvalidId()
|
||||
{
|
||||
Habit habit = Habit.get(123456L);
|
||||
assertThat(habit, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAll_withoutArchived()
|
||||
{
|
||||
List<Habit> habits = new LinkedList<>();
|
||||
List<Habit> habitsWithArchived = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
|
||||
if(i % 2 == 0)
|
||||
h.archived = 1;
|
||||
else
|
||||
habits.add(h);
|
||||
|
||||
habitsWithArchived.add(h);
|
||||
h.save();
|
||||
}
|
||||
|
||||
assertThat(habits, equalTo(Habit.getAll(false)));
|
||||
assertThat(habitsWithArchived, equalTo(Habit.getAll(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getByPosition()
|
||||
{
|
||||
List<Habit> habits = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
h.save();
|
||||
habits.add(h);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit h = Habit.getByPosition(i);
|
||||
if(h == null) fail();
|
||||
assertThat(h, equalTo(habits.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_count()
|
||||
{
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
if(i % 2 == 0) h.archived = 1;
|
||||
h.save();
|
||||
}
|
||||
|
||||
assertThat(Habit.count(), equalTo(5));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test_countWithArchived()
|
||||
{
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
if(i % 2 == 0) h.archived = 1;
|
||||
h.save();
|
||||
}
|
||||
|
||||
assertThat(Habit.countWithArchived(), equalTo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_updateId()
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.name = "Hello World";
|
||||
habit.save();
|
||||
|
||||
Long oldId = habit.getId();
|
||||
Long newId = 123456L;
|
||||
Habit.updateId(oldId, newId);
|
||||
|
||||
Habit newHabit = Habit.get(newId);
|
||||
if(newHabit == null) fail();
|
||||
assertThat(newHabit, is(not(nullValue())));
|
||||
assertThat(newHabit.name, equalTo(habit.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_reorder()
|
||||
{
|
||||
List<Long> ids = new LinkedList<>();
|
||||
|
||||
int n = 10;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
h.save();
|
||||
ids.add(h.getId());
|
||||
assertThat(h.position, is(i));
|
||||
}
|
||||
|
||||
int operations[][] = {
|
||||
{5, 2},
|
||||
{3, 7},
|
||||
{4, 4},
|
||||
{3, 2}
|
||||
};
|
||||
|
||||
int expectedPosition[][] = {
|
||||
{0, 1, 3, 4, 5, 2, 6, 7, 8, 9},
|
||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
||||
{0, 1, 7, 2, 4, 3, 5, 6, 8, 9},
|
||||
};
|
||||
|
||||
for(int i = 0; i < operations.length; i++)
|
||||
{
|
||||
int from = operations[i][0];
|
||||
int to = operations[i][1];
|
||||
|
||||
Habit fromHabit = Habit.getByPosition(from);
|
||||
Habit toHabit = Habit.getByPosition(to);
|
||||
Habit.reorder(fromHabit, toHabit);
|
||||
|
||||
int actualPositions[] = new int[n];
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
Habit h = Habit.get(ids.get(j));
|
||||
if (h == null) fail();
|
||||
actualPositions[j] = h.position;
|
||||
}
|
||||
|
||||
assertThat(actualPositions, equalTo(expectedPosition[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_rebuildOrder()
|
||||
{
|
||||
List<Long> ids = new LinkedList<>();
|
||||
int originalPositions[] = { 0, 1, 1, 4, 6, 8, 10, 10, 13};
|
||||
|
||||
for (int p : originalPositions)
|
||||
{
|
||||
Habit h = new Habit();
|
||||
h.position = p;
|
||||
h.save();
|
||||
ids.add(h.getId());
|
||||
}
|
||||
|
||||
Habit.rebuildOrder();
|
||||
|
||||
for (int i = 0; i < originalPositions.length; i++)
|
||||
{
|
||||
Habit h = Habit.get(ids.get(i));
|
||||
if(h == null) fail();
|
||||
assertThat(h.position, is(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getHabitsWithReminder()
|
||||
{
|
||||
List<Habit> habitsWithReminder = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
if(i % 2 == 0)
|
||||
{
|
||||
habit.reminderDays = DateHelper.ALL_WEEK_DAYS;
|
||||
habit.reminderHour = 8;
|
||||
habit.reminderMin = 30;
|
||||
habitsWithReminder.add(habit);
|
||||
}
|
||||
habit.save();
|
||||
}
|
||||
|
||||
assertThat(habitsWithReminder, equalTo(Habit.getHabitsWithReminder()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_archive_unarchive()
|
||||
{
|
||||
List<Habit> allHabits = new LinkedList<>();
|
||||
List<Habit> archivedHabits = new LinkedList<>();
|
||||
List<Habit> unarchivedHabits = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.save();
|
||||
allHabits.add(habit);
|
||||
|
||||
if(i % 2 == 0)
|
||||
archivedHabits.add(habit);
|
||||
else
|
||||
unarchivedHabits.add(habit);
|
||||
}
|
||||
|
||||
Habit.archive(archivedHabits);
|
||||
assertThat(Habit.getAll(false), equalTo(unarchivedHabits));
|
||||
assertThat(Habit.getAll(true), equalTo(allHabits));
|
||||
|
||||
Habit.unarchive(archivedHabits);
|
||||
assertThat(Habit.getAll(false), equalTo(allHabits));
|
||||
assertThat(Habit.getAll(true), equalTo(allHabits));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_setColor()
|
||||
{
|
||||
List<Habit> habits = new LinkedList<>();
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
Habit habit = new Habit();
|
||||
habit.color = i;
|
||||
habit.save();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
int newColor = 100;
|
||||
Habit.setColor(habits, newColor);
|
||||
|
||||
for(Habit h : habits)
|
||||
assertThat(h.color, equalTo(newColor));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_hasReminder_clearReminder()
|
||||
{
|
||||
Habit h = new Habit();
|
||||
assertThat(h.hasReminder(), is(false));
|
||||
|
||||
h.reminderDays = DateHelper.ALL_WEEK_DAYS;
|
||||
h.reminderHour = 8;
|
||||
h.reminderMin = 30;
|
||||
assertThat(h.hasReminder(), is(true));
|
||||
|
||||
h.clearReminder();
|
||||
assertThat(h.hasReminder(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
HabitFixtures.createEmptyHabit();
|
||||
HabitFixtures.createShortHabit();
|
||||
|
||||
String expectedCSV =
|
||||
"Name,Description,NumRepetitions,Interval,Color\n" +
|
||||
"Meditate,Did you meditate this morning?,1,1,#AFB42B\n" +
|
||||
"Wake up early,Did you wake up before 6am?,2,3,#00897B\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
Habit.writeCSV(Habit.getAll(true), writer);
|
||||
|
||||
MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Repetition;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Random;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class RepetitionListTest extends BaseTest
|
||||
{
|
||||
private Habit habit;
|
||||
private Habit emptyHabit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
emptyHabit = HabitFixtures.createEmptyHabit();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
{
|
||||
DateHelper.setFixedLocalTime(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_contains()
|
||||
{
|
||||
long current = DateHelper.getStartOfToday();
|
||||
|
||||
for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS)
|
||||
{
|
||||
assertThat(habit.repetitions.contains(current), equalTo(b));
|
||||
current -= DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
assertThat(habit.repetitions.contains(current), equalTo(false));
|
||||
current -= DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_delete()
|
||||
{
|
||||
long timestamp = DateHelper.getStartOfToday();
|
||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
||||
|
||||
habit.repetitions.delete(timestamp);
|
||||
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_toggle()
|
||||
{
|
||||
long timestamp = DateHelper.getStartOfToday();
|
||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
||||
|
||||
habit.repetitions.toggle(timestamp);
|
||||
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
|
||||
|
||||
habit.repetitions.toggle(timestamp);
|
||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getWeekDayFrequency()
|
||||
{
|
||||
Random random = new Random();
|
||||
Integer weekdayCount[][] = new Integer[12][7];
|
||||
Integer monthCount[] = new Integer[12];
|
||||
|
||||
Arrays.fill(monthCount, 0);
|
||||
for(Integer row[] : weekdayCount)
|
||||
Arrays.fill(row, 0);
|
||||
|
||||
GregorianCalendar day = DateHelper.getStartOfTodayCalendar();
|
||||
|
||||
// Sets the current date to the end of November
|
||||
day.set(2015, 10, 30);
|
||||
DateHelper.setFixedLocalTime(day.getTimeInMillis());
|
||||
|
||||
// Add repetitions randomly from January to December
|
||||
// Leaves the month of March empty, to check that it returns null
|
||||
day.set(2015, 0, 1);
|
||||
for(int i = 0; i < 365; i ++)
|
||||
{
|
||||
if(random.nextBoolean())
|
||||
{
|
||||
int month = day.get(Calendar.MONTH);
|
||||
int week = day.get(Calendar.DAY_OF_WEEK) % 7;
|
||||
|
||||
if(month != 2)
|
||||
{
|
||||
if (month <= 10)
|
||||
{
|
||||
weekdayCount[month][week]++;
|
||||
monthCount[month]++;
|
||||
}
|
||||
emptyHabit.repetitions.toggle(day.getTimeInMillis());
|
||||
}
|
||||
}
|
||||
|
||||
day.add(Calendar.DAY_OF_YEAR, 1);
|
||||
}
|
||||
|
||||
HashMap<Long, Integer[]> freq = emptyHabit.repetitions.getWeekdayFrequency();
|
||||
|
||||
// Repetitions until November should be counted correctly
|
||||
for(int month = 0; month < 11; month++)
|
||||
{
|
||||
day.set(2015, month, 1);
|
||||
Integer actualCount[] = freq.get(day.getTimeInMillis());
|
||||
if(monthCount[month] == 0)
|
||||
assertThat(actualCount, equalTo(null));
|
||||
else
|
||||
assertThat(actualCount, equalTo(weekdayCount[month]));
|
||||
}
|
||||
|
||||
// Repetitions in December should be discarded
|
||||
day.set(2015, 11, 1);
|
||||
assertThat(freq.get(day.getTimeInMillis()), equalTo(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_count()
|
||||
{
|
||||
long to = DateHelper.getStartOfToday();
|
||||
long from = to - 9 * DateHelper.millisecondsInOneDay;
|
||||
assertThat(habit.repetitions.count(from, to), equalTo(6));
|
||||
|
||||
to = DateHelper.getStartOfToday() - DateHelper.millisecondsInOneDay;
|
||||
from = to - 5 * DateHelper.millisecondsInOneDay;
|
||||
assertThat(habit.repetitions.count(from, to), equalTo(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getOldest()
|
||||
{
|
||||
long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay;
|
||||
|
||||
assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp));
|
||||
|
||||
Repetition oldest = habit.repetitions.getOldest();
|
||||
assertFalse(oldest == null);
|
||||
assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp));
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ScoreListTest extends BaseTest
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
habit = HabitFixtures.createEmptyHabit();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
{
|
||||
DateHelper.setFixedLocalTime(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_invalidateNewerThan()
|
||||
{
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(0));
|
||||
|
||||
toggleRepetitions(0, 2);
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(1948077));
|
||||
|
||||
habit.freqNum = 1;
|
||||
habit.freqDen = 2;
|
||||
habit.scores.invalidateNewerThan(0);
|
||||
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(1974654));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayStarValue()
|
||||
{
|
||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.EMPTY_STAR));
|
||||
|
||||
int k = 0;
|
||||
while(habit.scores.getTodayValue() < Score.HALF_STAR_CUTOFF) toggleRepetitions(k, ++k);
|
||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.HALF_STAR));
|
||||
|
||||
while(habit.scores.getTodayValue() < Score.FULL_STAR_CUTOFF) toggleRepetitions(k, ++k);
|
||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.FULL_STAR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
assertThat(habit.scores.getTodayValue(), equalTo(12629351));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
|
||||
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
|
||||
4507040, 3699107, 2846927, 1948077, 1000000 };
|
||||
|
||||
long current = DateHelper.getStartOfToday();
|
||||
for(int expectedValue : expectedValues)
|
||||
{
|
||||
assertThat(habit.scores.getValue(current), equalTo(expectedValue));
|
||||
current -= DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withoutGroups()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
|
||||
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
|
||||
4507040, 3699107, 2846927, 1948077, 1000000 };
|
||||
|
||||
int actualValues[] = habit.scores.getAllValues(1);
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withGroups()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = { 11434978, 7894999, 3212362 };
|
||||
|
||||
int actualValues[] = habit.scores.getAllValues(7);
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
HabitFixtures.purgeHabits();
|
||||
Habit habit = HabitFixtures.createShortHabit();
|
||||
|
||||
String expectedCSV =
|
||||
"2015-01-16,0.0519\n" +
|
||||
"2015-01-17,0.1021\n" +
|
||||
"2015-01-18,0.0986\n" +
|
||||
"2015-01-19,0.0952\n" +
|
||||
"2015-01-20,0.1439\n" +
|
||||
"2015-01-21,0.1909\n" +
|
||||
"2015-01-22,0.2364\n" +
|
||||
"2015-01-23,0.2283\n" +
|
||||
"2015-01-24,0.2205\n" +
|
||||
"2015-01-25,0.2649\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
habit.scores.writeCSV(writer);
|
||||
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
private void toggleRepetitions(final int from, final int to)
|
||||
{
|
||||
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
|
||||
{
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
long today = DateHelper.getStartOfToday();
|
||||
for (int i = from; i < to; i++)
|
||||
habit.repetitions.toggle(today - i * DateHelper.millisecondsInOneDay);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ScoreTest extends BaseTest
|
||||
{
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_compute_withDailyHabit()
|
||||
{
|
||||
int checkmark = Checkmark.UNCHECKED;
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
|
||||
|
||||
checkmark = Checkmark.CHECKED_IMPLICITLY;
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
|
||||
|
||||
checkmark = Checkmark.CHECKED_EXPLICITLY;
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_compute_withNonDailyHabit()
|
||||
{
|
||||
int checkmark = Checkmark.CHECKED_EXPLICITLY;
|
||||
assertThat(Score.compute(1/3.0, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1/3.0, 5000000, checkmark), equalTo(5916180));
|
||||
assertThat(Score.compute(1/3.0, 10000000, checkmark), equalTo(10832360));
|
||||
assertThat(Score.compute(1/3.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
||||
|
||||
assertThat(Score.compute(1/7.0, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1/7.0, 5000000, checkmark), equalTo(5964398));
|
||||
assertThat(Score.compute(1/7.0, 10000000, checkmark), equalTo(10928796));
|
||||
assertThat(Score.compute(1/7.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getStarStatus()
|
||||
{
|
||||
Score s = new Score();
|
||||
|
||||
s.score = Score.FULL_STAR_CUTOFF + 1;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
|
||||
|
||||
s.score = Score.FULL_STAR_CUTOFF;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
|
||||
|
||||
s.score = Score.FULL_STAR_CUTOFF - 1;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
||||
|
||||
s.score = Score.HALF_STAR_CUTOFF + 1;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
||||
|
||||
s.score = Score.HALF_STAR_CUTOFF;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
||||
|
||||
s.score = Score.HALF_STAR_CUTOFF - 1;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
|
||||
|
||||
s.score = 0;
|
||||
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.ExportCSVTask;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ExportCSVTaskTest extends BaseTest
|
||||
{
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportCSV() throws Throwable
|
||||
{
|
||||
HabitFixtures.createShortHabit();
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
ProgressBar bar = new ProgressBar(targetContext);
|
||||
|
||||
ExportCSVTask task = new ExportCSVTask(habits, bar);
|
||||
task.setListener(new ExportCSVTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onExportCSVFinished(String archiveFilename)
|
||||
{
|
||||
assertThat(archiveFilename, is(not(nullValue())));
|
||||
|
||||
File f = new File(archiveFilename);
|
||||
assertTrue(f.exists());
|
||||
assertTrue(f.canRead());
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
waitForAsyncTasks();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ExportDBTaskTest extends BaseTest
|
||||
{
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportCSV() throws Throwable
|
||||
{
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
|
||||
ProgressBar bar = new ProgressBar(context);
|
||||
ExportDBTask task = new ExportDBTask(bar);
|
||||
task.setListener(new ExportDBTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onExportDBFinished(String filename)
|
||||
{
|
||||
assertThat(filename, is(not(nullValue())));
|
||||
|
||||
File f = new File(filename);
|
||||
assertTrue(f.exists());
|
||||
assertTrue(f.canRead());
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
waitForAsyncTasks();
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ImportDataTaskTest extends BaseTest
|
||||
{
|
||||
private File baseDir;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
baseDir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(baseDir == null) fail("baseDir should not be null");
|
||||
}
|
||||
|
||||
private void copyAssetToFile(String assetPath, File dst) throws IOException
|
||||
{
|
||||
InputStream in = testContext.getAssets().open(assetPath);
|
||||
DatabaseHelper.copy(in, dst);
|
||||
}
|
||||
|
||||
private void assertTaskResult(final int expectedResult, String assetFilename) throws Throwable
|
||||
{
|
||||
ImportDataTask task = createTask(assetFilename);
|
||||
|
||||
task.setListener(new ImportDataTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onImportFinished(int result)
|
||||
{
|
||||
assertThat(result, equalTo(expectedResult));
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
waitForAsyncTasks();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ImportDataTask createTask(String assetFilename) throws IOException
|
||||
{
|
||||
ProgressBar bar = new ProgressBar(targetContext);
|
||||
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
|
||||
copyAssetToFile(assetFilename, file);
|
||||
|
||||
return new ImportDataTask(file, bar);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportInvalidData() throws Throwable
|
||||
{
|
||||
assertTaskResult(ImportDataTask.NOT_RECOGNIZED, "icon.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportValidData() throws Throwable
|
||||
{
|
||||
assertTaskResult(ImportDataTask.SUCCESS, "loop.db");
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.CheckmarkWidgetView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class CheckmarkWidgetViewTest extends ViewTest
|
||||
{
|
||||
private CheckmarkWidgetView view;
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
UIHelper.setFixedTheme(R.style.TransparentWidgetTheme);
|
||||
|
||||
habit = HabitFixtures.createShortHabit();
|
||||
view = new CheckmarkWidgetView(targetContext);
|
||||
view.setHabit(habit);
|
||||
refreshData(view);
|
||||
measureView(dpToPixels(100), dpToPixels(200), view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_checked() throws IOException
|
||||
{
|
||||
assertRenders(view, "CheckmarkView/checked.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_unchecked() throws IOException
|
||||
{
|
||||
habit.repetitions.toggle(DateHelper.getStartOfToday());
|
||||
view.refreshData();
|
||||
|
||||
assertRenders(view, "CheckmarkView/unchecked.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_implicitlyChecked() throws IOException
|
||||
{
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
habit.repetitions.toggle(today);
|
||||
habit.repetitions.toggle(today - day);
|
||||
habit.repetitions.toggle(today - 2 * day);
|
||||
view.refreshData();
|
||||
|
||||
assertRenders(view, "CheckmarkView/implicitly_checked.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_largeSize() throws IOException
|
||||
{
|
||||
measureView(dpToPixels(300), dpToPixels(300), view);
|
||||
assertRenders(view, "CheckmarkView/large_size.png");
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.HabitFrequencyView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitFrequencyViewTest extends ViewTest
|
||||
{
|
||||
private HabitFrequencyView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
Habit habit = HabitFixtures.createLongHabit();
|
||||
|
||||
view = new HabitFrequencyView(targetContext);
|
||||
view.setHabit(habit);
|
||||
refreshData(view);
|
||||
measureView(dpToPixels(300), dpToPixels(100), view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Throwable
|
||||
{
|
||||
assertRenders(view, "HabitFrequencyView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withTransparentBackground() throws Throwable
|
||||
{
|
||||
view.setIsBackgroundTransparent(true);
|
||||
assertRenders(view, "HabitFrequencyView/renderTransparent.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentSize() throws Throwable
|
||||
{
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "HabitFrequencyView/renderDifferentSize.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDataOffset() throws Throwable
|
||||
{
|
||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
||||
view.invalidate();
|
||||
|
||||
assertRenders(view, "HabitFrequencyView/renderDataOffset.png");
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitHistoryViewTest extends ViewTest
|
||||
{
|
||||
private Habit habit;
|
||||
private HabitHistoryView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
habit = HabitFixtures.createLongHabit();
|
||||
|
||||
view = new HabitHistoryView(targetContext);
|
||||
view.setHabit(habit);
|
||||
measureView(dpToPixels(400), dpToPixels(200), view);
|
||||
refreshData(view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Throwable
|
||||
{
|
||||
assertRenders(view, "HabitHistoryView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withTransparentBackground() throws Throwable
|
||||
{
|
||||
view.setIsBackgroundTransparent(true);
|
||||
assertRenders(view, "HabitHistoryView/renderTransparent.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentSize() throws Throwable
|
||||
{
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "HabitHistoryView/renderDifferentSize.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDataOffset() throws Throwable
|
||||
{
|
||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
||||
view.invalidate();
|
||||
|
||||
assertRenders(view, "HabitHistoryView/renderDataOffset.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tapDate_withEditableView() throws Throwable
|
||||
{
|
||||
view.setIsEditable(true);
|
||||
tap(view, 340, 40); // today's square
|
||||
waitForAsyncTasks();
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
assertFalse(habit.repetitions.contains(today));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tapDate_atInvalidLocations() throws Throwable
|
||||
{
|
||||
int expectedCheckmarkValues[] = habit.checkmarks.getAllValues();
|
||||
|
||||
view.setIsEditable(true);
|
||||
tap(view, 118, 13); // header
|
||||
tap(view, 336, 60); // tomorrow's square
|
||||
tap(view, 370, 60); // right axis
|
||||
waitForAsyncTasks();
|
||||
|
||||
int actualCheckmarkValues[] = habit.checkmarks.getAllValues();
|
||||
assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tapDate_withReadOnlyView() throws Throwable
|
||||
{
|
||||
view.setIsEditable(false);
|
||||
tap(view, 340, 40); // today's square
|
||||
waitForAsyncTasks();
|
||||
|
||||
long today = DateHelper.getStartOfToday();
|
||||
assertTrue(habit.repetitions.contains(today));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.HabitScoreView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitScoreViewTest extends ViewTest
|
||||
{
|
||||
private Habit habit;
|
||||
private HabitScoreView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
habit = HabitFixtures.createLongHabit();
|
||||
|
||||
view = new HabitScoreView(targetContext);
|
||||
view.setHabit(habit);
|
||||
view.setBucketSize(7);
|
||||
refreshData(view);
|
||||
measureView(dpToPixels(300), dpToPixels(200), view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Throwable
|
||||
{
|
||||
Log.d("HabitScoreViewTest", String.format("height=%d", dpToPixels(100)));
|
||||
assertRenders(view, "HabitScoreView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withTransparentBackground() throws Throwable
|
||||
{
|
||||
view.setIsTransparencyEnabled(true);
|
||||
assertRenders(view, "HabitScoreView/renderTransparent.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentSize() throws Throwable
|
||||
{
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "HabitScoreView/renderDifferentSize.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDataOffset() throws Throwable
|
||||
{
|
||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
||||
view.invalidate();
|
||||
|
||||
assertRenders(view, "HabitScoreView/renderDataOffset.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withMonthlyBucket() throws Throwable
|
||||
{
|
||||
view.setBucketSize(30);
|
||||
view.refreshData();
|
||||
view.invalidate();
|
||||
|
||||
assertRenders(view, "HabitScoreView/renderMonthly.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withYearlyBucket() throws Throwable
|
||||
{
|
||||
view.setBucketSize(365);
|
||||
view.refreshData();
|
||||
view.invalidate();
|
||||
|
||||
assertRenders(view, "HabitScoreView/renderYearly.png");
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.unit.HabitFixtures;
|
||||
import org.isoron.uhabits.views.HabitStreakView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class HabitStreakViewTest extends ViewTest
|
||||
{
|
||||
private HabitStreakView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
HabitFixtures.purgeHabits();
|
||||
Habit habit = HabitFixtures.createLongHabit();
|
||||
|
||||
view = new HabitStreakView(targetContext);
|
||||
measureView(dpToPixels(300), dpToPixels(100), view);
|
||||
|
||||
view.setHabit(habit);
|
||||
refreshData(view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender() throws Throwable
|
||||
{
|
||||
assertRenders(view, "HabitStreakView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withTransparentBackground() throws Throwable
|
||||
{
|
||||
view.setIsBackgroundTransparent(true);
|
||||
assertRenders(view, "HabitStreakView/renderTransparent.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withSmallSize() throws Throwable
|
||||
{
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
refreshData(view);
|
||||
|
||||
assertRenders(view, "HabitStreakView/renderSmallSize.png");
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.views.NumberView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NumberViewTest extends ViewTest
|
||||
{
|
||||
private NumberView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
view = new NumberView(targetContext);
|
||||
view.setLabel("Hello world");
|
||||
view.setNumber(31);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[0]);
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_base() throws IOException
|
||||
{
|
||||
assertRenders(view, "NumberView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withLongLabel() throws IOException
|
||||
{
|
||||
view.setLabel("The quick brown fox jumps over the lazy fox");
|
||||
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
assertRenders(view, "NumberView/renderLongLabel.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentParams() throws IOException
|
||||
{
|
||||
view.setNumber(500);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[5]);
|
||||
view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize));
|
||||
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "NumberView/renderDifferentParams.png");
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.views.RingView;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class RingViewTest extends ViewTest
|
||||
{
|
||||
private RingView view;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
super.setup();
|
||||
|
||||
view = new RingView(targetContext);
|
||||
view.setPercentage(0.6f);
|
||||
view.setText("60%");
|
||||
view.setColor(ColorHelper.CSV_PALETTE[0]);
|
||||
view.setBackgroundColor(Color.WHITE);
|
||||
view.setThickness(dpToPixels(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_base() throws IOException
|
||||
{
|
||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||
assertRenders(view, "RingView/render.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRender_withDifferentParams() throws IOException
|
||||
{
|
||||
view.setPercentage(0.25f);
|
||||
view.setColor(ColorHelper.CSV_PALETTE[5]);
|
||||
|
||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||
assertRenders(view, "RingView/renderDifferentParams.png");
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.views;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.SystemClock;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.BaseTest;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.views.HabitDataView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
public class ViewTest extends BaseTest
|
||||
{
|
||||
protected static final double SIMILARITY_CUTOFF = 0.09;
|
||||
public static final int HISTOGRAM_BIN_SIZE = 8;
|
||||
|
||||
protected void measureView(int width, int height, View view)
|
||||
{
|
||||
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
|
||||
int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
|
||||
|
||||
view.measure(specWidth, specHeight);
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
}
|
||||
|
||||
protected void assertRenders(View view, String expectedImagePath) throws IOException
|
||||
{
|
||||
StringBuilder errorMessage = new StringBuilder();
|
||||
expectedImagePath = getVersionedViewAssetPath(expectedImagePath);
|
||||
|
||||
view.setDrawingCacheEnabled(true);
|
||||
view.buildDrawingCache();
|
||||
Bitmap actual = view.getDrawingCache();
|
||||
Bitmap expected = getBitmapFromAssets(expectedImagePath);
|
||||
|
||||
int width = actual.getWidth();
|
||||
int height = actual.getHeight();
|
||||
Bitmap scaledExpected = Bitmap.createScaledBitmap(expected, width, height, true);
|
||||
|
||||
double distance;
|
||||
boolean similarEnough = true;
|
||||
|
||||
if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF)
|
||||
{
|
||||
similarEnough = false;
|
||||
errorMessage.append(String.format(
|
||||
"Rendered image has wrong histogram (distance=%f). ",
|
||||
distance));
|
||||
}
|
||||
|
||||
if(!similarEnough)
|
||||
{
|
||||
saveBitmap(expectedImagePath, ".expected", scaledExpected);
|
||||
String path = saveBitmap(expectedImagePath, "", actual);
|
||||
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
|
||||
fail(errorMessage.toString());
|
||||
}
|
||||
|
||||
actual.recycle();
|
||||
expected.recycle();
|
||||
scaledExpected.recycle();
|
||||
}
|
||||
|
||||
private Bitmap getBitmapFromAssets(String path) throws IOException
|
||||
{
|
||||
InputStream stream = testContext.getAssets().open(path);
|
||||
return BitmapFactory.decodeStream(stream);
|
||||
}
|
||||
|
||||
private String getVersionedViewAssetPath(String path)
|
||||
{
|
||||
String result = null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
try
|
||||
{
|
||||
String vpath = "views-v21/" + path;
|
||||
testContext.getAssets().open(vpath);
|
||||
result = vpath;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
if(result == null)
|
||||
result = "views/" + path;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
|
||||
throws IOException
|
||||
{
|
||||
File dir = DatabaseHelper.getSDCardDir("test-screenshots");
|
||||
if(dir == null) dir = DatabaseHelper.getFilesDir("test-screenshots");
|
||||
if(dir == null) throw new RuntimeException("Could not find suitable dir for screenshots");
|
||||
|
||||
filename = filename.replaceAll("\\.png$", suffix + ".png");
|
||||
String absolutePath = String.format("%s/%s", dir.getAbsolutePath(), filename);
|
||||
|
||||
File parent = new File(absolutePath).getParentFile();
|
||||
if(!parent.exists() && !parent.mkdirs())
|
||||
throw new RuntimeException(String.format("Could not create dir: %s",
|
||||
parent.getAbsolutePath()));
|
||||
|
||||
FileOutputStream out = new FileOutputStream(absolutePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
private int[][] getHistogram(Bitmap bitmap)
|
||||
{
|
||||
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
|
||||
|
||||
for(int x = 0; x < bitmap.getWidth(); x++)
|
||||
{
|
||||
for(int y = 0; y < bitmap.getHeight(); y++)
|
||||
{
|
||||
int color = bitmap.getPixel(x, y);
|
||||
int[] argb = new int[]{
|
||||
(color >> 24) & 0xff, //alpha
|
||||
(color >> 16) & 0xff, //red
|
||||
(color >> 8) & 0xff, //green
|
||||
(color ) & 0xff //blue
|
||||
};
|
||||
|
||||
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
|
||||
histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
|
||||
histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
|
||||
histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
|
||||
}
|
||||
}
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
private double compareHistograms(int[][] actualHistogram, int[][] expectedHistogram)
|
||||
{
|
||||
long diff = 0;
|
||||
long total = 0;
|
||||
|
||||
for(int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i ++)
|
||||
{
|
||||
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
|
||||
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
|
||||
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
|
||||
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
|
||||
|
||||
total += actualHistogram[0][i];
|
||||
total += actualHistogram[1][i];
|
||||
total += actualHistogram[2][i];
|
||||
total += actualHistogram[3][i];
|
||||
}
|
||||
|
||||
return (double) diff / total / 2;
|
||||
}
|
||||
|
||||
protected int dpToPixels(int dp)
|
||||
{
|
||||
return (int) UIHelper.dpToPixels(targetContext, dp);
|
||||
}
|
||||
|
||||
protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException
|
||||
{
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
|
||||
dpToPixels(y), 0);
|
||||
view.onSingleTapUp(e);
|
||||
e.recycle();
|
||||
}
|
||||
|
||||
protected void refreshData(final HabitDataView view)
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
view.refreshData();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
try
|
||||
{
|
||||
waitForAsyncTasks();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException("Time out");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
tools:replace="maxSdkVersion"
|
||||
android:maxSdkVersion="99" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:replace="maxSdkVersion"
|
||||
android:maxSdkVersion="99" />
|
||||
|
||||
</manifest>
|
||||
@@ -1,167 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
~
|
||||
~ This file is part of Loop Habit Tracker.
|
||||
~
|
||||
~ Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by the
|
||||
~ Free Software Foundation, either version 3 of the License, or (at your
|
||||
~ option) any later version.
|
||||
~
|
||||
~ Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
~ more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along
|
||||
~ with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="21"
|
||||
android:versionName="1.5.5">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name="HabitsApplication"
|
||||
android:allowBackup="true"
|
||||
android:backupAgent=".HabitsBackupAgent"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/main_activity_title"
|
||||
android:theme="@style/AppBaseTheme"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/main_activity_title">
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ShowHabitActivity"
|
||||
android:label="@string/title_activity_show_habit">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".IntroActivity"
|
||||
android:label=""
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
|
||||
|
||||
<activity
|
||||
android:name=".widgets.HabitPickerDialog"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/about">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.CheckmarkWidgetProvider"
|
||||
android:label="@string/checkmark">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_checkmark_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.HistoryWidgetProvider"
|
||||
android:label="@string/history">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_history_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.ScoreWidgetProvider"
|
||||
android:label="@string/habit_strength">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_score_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.StreakWidgetProvider"
|
||||
android:label="@string/streaks">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_streak_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.FrequencyWidgetProvider"
|
||||
android:label="@string/frequency">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_frequency_info"/>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".HabitBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1 +0,0 @@
|
||||
alter table habits add column reminder_days integer not null default 127;
|
||||
@@ -1,4 +0,0 @@
|
||||
create index idx_score_habit_timestamp on score(habit, timestamp);
|
||||
create index idx_checkmark_habit_timestamp on checkmarks(habit, timestamp);
|
||||
create index idx_repetitions_habit_timestamp on repetitions(habit, timestamp);
|
||||
create index idx_streak_habit_end on streak(habit, end);
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table habits add column reminder_hour integer;
|
||||
alter table habits add column reminder_min integer;
|
||||
@@ -1 +0,0 @@
|
||||
alter table habits add column highlight integer not null default 0;
|
||||
@@ -1 +0,0 @@
|
||||
alter table habits add column archived integer not null default 0;
|
||||
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
public class AboutActivity extends BaseActivity implements View.OnClickListener
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.about);
|
||||
setupSupportActionBar(true);
|
||||
|
||||
int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor);
|
||||
setupActionBarColor(color);
|
||||
|
||||
TextView tvVersion = (TextView) findViewById(R.id.tvVersion);
|
||||
TextView tvRate = (TextView) findViewById(R.id.tvRate);
|
||||
TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback);
|
||||
TextView tvSource = (TextView) findViewById(R.id.tvSource);
|
||||
|
||||
tvVersion.setText(String.format(getResources().getString(R.string.version_n),
|
||||
BuildConfig.VERSION_NAME));
|
||||
tvRate.setOnClickListener(this);
|
||||
tvFeedback.setOnClickListener(this);
|
||||
tvSource.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
switch (v.getId())
|
||||
{
|
||||
case R.id.tvRate:
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(getString(R.string.playStoreURL)));
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.tvFeedback:
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse(getString(R.string.feedbackURL)));
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.tvSource:
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(getString(R.string.sourceCodeURL)));
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler
|
||||
{
|
||||
private static int MAX_UNDO_LEVEL = 15;
|
||||
|
||||
private LinkedList<Command> undoList;
|
||||
private LinkedList<Command> redoList;
|
||||
private Toast toast;
|
||||
|
||||
Thread.UncaughtExceptionHandler androidExceptionHandler;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
UIHelper.applyCurrentTheme(this);
|
||||
|
||||
androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
|
||||
undoList = new LinkedList<>();
|
||||
redoList = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void executeCommand(Command command, Long refreshKey)
|
||||
{
|
||||
executeCommand(command, false, refreshKey);
|
||||
}
|
||||
|
||||
protected void undo()
|
||||
{
|
||||
if (undoList.isEmpty())
|
||||
{
|
||||
showToast(R.string.toast_nothing_to_undo);
|
||||
return;
|
||||
}
|
||||
|
||||
Command last = undoList.pop();
|
||||
redoList.push(last);
|
||||
last.undo();
|
||||
showToast(last.getUndoStringId());
|
||||
}
|
||||
|
||||
protected void redo()
|
||||
{
|
||||
if (redoList.isEmpty())
|
||||
{
|
||||
showToast(R.string.toast_nothing_to_redo);
|
||||
return;
|
||||
}
|
||||
Command last = redoList.pop();
|
||||
executeCommand(last, false, null);
|
||||
}
|
||||
|
||||
public void showToast(Integer stringId)
|
||||
{
|
||||
if (stringId == null) return;
|
||||
if (toast == null) toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT);
|
||||
else toast.setText(stringId);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
public void executeCommand(final Command command, Boolean clearRedoStack, final Long refreshKey)
|
||||
{
|
||||
undoList.push(command);
|
||||
|
||||
if (undoList.size() > MAX_UNDO_LEVEL) undoList.removeLast();
|
||||
if (clearRedoStack) redoList.clear();
|
||||
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
command.execute();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
BaseActivity.this.onPostExecuteCommand(refreshKey);
|
||||
BackupManager.dataChanged("org.isoron.uhabits");
|
||||
}
|
||||
}.execute();
|
||||
|
||||
|
||||
showToast(command.getExecuteStringId());
|
||||
}
|
||||
|
||||
protected void setupSupportActionBar(boolean homeButtonEnabled)
|
||||
{
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if(toolbar == null) return;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
toolbar.setElevation(UIHelper.dpToPixels(this, 2));
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
if(homeButtonEnabled)
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
ex.printStackTrace();
|
||||
HabitsApplication.dumpBugReportToFile();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if(androidExceptionHandler != null)
|
||||
androidExceptionHandler.uncaughtException(thread, ex);
|
||||
else
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
protected void setupActionBarColor(int color)
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
if (!UIHelper.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return;
|
||||
|
||||
ColorDrawable drawable = new ColorDrawable(color);
|
||||
actionBar.setBackgroundDrawable(drawable);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
{
|
||||
int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f);
|
||||
getWindow().setStatusBarColor(darkerColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume()
|
||||
{
|
||||
super.onPostResume();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
hideFakeToolbarShadow();
|
||||
}
|
||||
|
||||
protected void hideFakeToolbarShadow()
|
||||
{
|
||||
View view = findViewById(R.id.toolbarShadow);
|
||||
if(view != null) view.setVisibility(View.GONE);
|
||||
|
||||
view = findViewById(R.id.headerShadow);
|
||||
if(view != null) view.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
|
||||
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
|
||||
public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
|
||||
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent)
|
||||
{
|
||||
switch (intent.getAction())
|
||||
{
|
||||
case ACTION_SHOW_REMINDER:
|
||||
createNotification(context, intent);
|
||||
createReminderAlarmsDelayed(context);
|
||||
break;
|
||||
|
||||
case ACTION_DISMISS:
|
||||
dismissAllHabits();
|
||||
break;
|
||||
|
||||
case ACTION_CHECK:
|
||||
checkHabit(context, intent);
|
||||
break;
|
||||
|
||||
case ACTION_SNOOZE:
|
||||
snoozeHabit(context, intent);
|
||||
break;
|
||||
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
ReminderHelper.createReminderAlarms(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createReminderAlarmsDelayed(final Context context)
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ReminderHelper.createReminderAlarms(context);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private void snoozeHabit(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
|
||||
|
||||
long habitId = ContentUris.parseId(data);
|
||||
Habit habit = Habit.get(habitId);
|
||||
if(habit != null)
|
||||
ReminderHelper.createReminderAlarm(context, habit,
|
||||
new Date().getTime() + delayMinutes * 60 * 1000);
|
||||
dismissNotification(context, habitId);
|
||||
}
|
||||
|
||||
private void checkHabit(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
|
||||
long habitId = ContentUris.parseId(data);
|
||||
Habit habit = Habit.get(habitId);
|
||||
if(habit != null)
|
||||
habit.repetitions.toggle(timestamp);
|
||||
dismissNotification(context, habitId);
|
||||
|
||||
sendRefreshBroadcast(context);
|
||||
}
|
||||
|
||||
public static void sendRefreshBroadcast(Context context)
|
||||
{
|
||||
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
|
||||
Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH);
|
||||
manager.sendBroadcast(refreshIntent);
|
||||
|
||||
MainActivity.updateWidgets(context);
|
||||
}
|
||||
|
||||
private void dismissAllHabits()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void dismissNotification(Context context, Long habitId)
|
||||
{
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habitId % Integer.MAX_VALUE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
|
||||
|
||||
private void createNotification(final Context context, final Intent intent)
|
||||
{
|
||||
final Uri data = intent.getData();
|
||||
final Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
final Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
final Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
|
||||
|
||||
if (habit == null) return;
|
||||
|
||||
new BaseTask()
|
||||
{
|
||||
int todayValue;
|
||||
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
todayValue = habit.checkmarks.getTodayValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if (todayValue != Checkmark.UNCHECKED) return;
|
||||
if (!checkWeekday(intent, habit)) return;
|
||||
if (!habit.hasReminder()) return;
|
||||
|
||||
Intent contentIntent = new Intent(context, MainActivity.class);
|
||||
contentIntent.setData(data);
|
||||
PendingIntent contentPendingIntent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
PendingIntent dismissPendingIntent = buildDismissIntent(context);
|
||||
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
|
||||
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
|
||||
|
||||
Uri ringtoneUri = ReminderHelper.getRingtoneUri(context);
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender =
|
||||
new NotificationCompat.WearableExtender().setBackground(
|
||||
BitmapFactory.decodeResource(context.getResources(),
|
||||
R.drawable.stripe));
|
||||
|
||||
Notification notification =
|
||||
new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(habit.name)
|
||||
.setContentText(habit.description)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.setDeleteIntent(dismissPendingIntent)
|
||||
.addAction(R.drawable.ic_action_check,
|
||||
context.getString(R.string.check), checkIntentPending)
|
||||
.addAction(R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze), snoozeIntentPending)
|
||||
.setSound(ringtoneUri)
|
||||
.extend(wearableExtender)
|
||||
.setWhen(reminderTime)
|
||||
.setShowWhen(true)
|
||||
.build();
|
||||
|
||||
notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.notify(notificationId, notification);
|
||||
|
||||
super.onPostExecute(aVoid);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
snoozeIntent.setData(data);
|
||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
checkIntent.setData(data);
|
||||
checkIntent.setAction(ACTION_CHECK);
|
||||
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
||||
return PendingIntent.getBroadcast(context, 0, checkIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildDismissIntent(Context context)
|
||||
{
|
||||
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
deleteIntent.setAction(ACTION_DISMISS);
|
||||
return PendingIntent.getBroadcast(context, 0, deleteIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildViewHabitIntent(Context context, Habit habit)
|
||||
{
|
||||
Intent intent = new Intent(context, ShowHabitActivity.class);
|
||||
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
|
||||
|
||||
return TaskStackBuilder.create(context.getApplicationContext())
|
||||
.addNextIntentWithParentStack(intent)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private boolean checkWeekday(Intent intent, Habit habit)
|
||||
{
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
|
||||
boolean reminderDays[] = DateHelper.unpackWeekdayList(habit.reminderDays);
|
||||
int weekday = DateHelper.getWeekday(timestamp);
|
||||
|
||||
return reminderDays[weekday];
|
||||
}
|
||||
|
||||
public static void dismissNotification(Context context, Habit habit)
|
||||
{
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(
|
||||
Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class HabitsApplication extends Application
|
||||
{
|
||||
@Nullable
|
||||
private static Context context;
|
||||
|
||||
public static boolean isTestMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(context != null)
|
||||
context.getClassLoader().loadClass("org.isoron.uhabits.unit.models.HabitTest");
|
||||
return true;
|
||||
}
|
||||
catch (final Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Context getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
HabitsApplication.context = this;
|
||||
|
||||
if (isTestMode())
|
||||
{
|
||||
File db = DatabaseHelper.getDatabaseFile();
|
||||
if(db.exists()) db.delete();
|
||||
}
|
||||
|
||||
DatabaseHelper.initializeActiveAndroid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate()
|
||||
{
|
||||
HabitsApplication.context = null;
|
||||
ActiveAndroid.dispose();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
public static String getLogcat() throws IOException
|
||||
{
|
||||
int maxNLines = 250;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] command = new String[] { "logcat", "-d"};
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
|
||||
InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
BufferedReader bufferedReader = new BufferedReader(in);
|
||||
|
||||
LinkedList<String> log = new LinkedList<>();
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null)
|
||||
{
|
||||
log.addLast(line);
|
||||
if(log.size() > maxNLines) log.removeFirst();
|
||||
}
|
||||
|
||||
for(String l : log)
|
||||
{
|
||||
builder.append(l);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String getDeviceInfo()
|
||||
{
|
||||
if(context == null) return "";
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
|
||||
b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME));
|
||||
b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE));
|
||||
b.append(String.format("OS Version: %s (%s)\n", System.getProperty("os.version"),
|
||||
android.os.Build.VERSION.INCREMENTAL));
|
||||
b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK));
|
||||
b.append(String.format("Device: %s\n", android.os.Build.DEVICE));
|
||||
b.append(String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL,
|
||||
android.os.Build.PRODUCT));
|
||||
b.append(String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER));
|
||||
b.append(String.format("Other tags: %s\n", android.os.Build.TAGS));
|
||||
b.append(String.format("Screen Width: %s\n", wm.getDefaultDisplay().getWidth()));
|
||||
b.append(String.format("Screen Height: %s\n", wm.getDefaultDisplay().getHeight()));
|
||||
b.append(String.format("SD Card state: %s\n\n", Environment.getExternalStorageState()));
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static File dumpBugReportToFile() throws IOException
|
||||
{
|
||||
String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime());
|
||||
|
||||
if(context == null) throw new RuntimeException("application context should not be null");
|
||||
File dir = DatabaseHelper.getFilesDir("Logs");
|
||||
if (dir == null) throw new IOException("log dir should not be null");
|
||||
|
||||
File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date));
|
||||
FileWriter output = new FileWriter(logFile);
|
||||
output.write(generateBugReport());
|
||||
output.close();
|
||||
|
||||
return logFile;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String generateBugReport() throws IOException
|
||||
{
|
||||
String logcat = getLogcat();
|
||||
String deviceInfo = getDeviceInfo();
|
||||
return deviceInfo + "\n" + logcat;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.github.paolorotolo.appintro.AppIntro2;
|
||||
import com.github.paolorotolo.appintro.AppIntroFragment;
|
||||
|
||||
public class IntroActivity extends AppIntro2
|
||||
{
|
||||
@Override
|
||||
public void init(Bundle savedInstanceState)
|
||||
{
|
||||
showStatusBar(false);
|
||||
|
||||
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1),
|
||||
getString(R.string.intro_description_1), R.drawable.intro_icon_1,
|
||||
Color.parseColor("#194673")));
|
||||
|
||||
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2),
|
||||
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
|
||||
Color.parseColor("#ffa726")));
|
||||
|
||||
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
|
||||
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
|
||||
Color.parseColor("#9575cd")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNextPressed()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDonePressed()
|
||||
{
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlideChanged()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.uhabits.fragments.ListHabitsFragment;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.FrequencyWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.HistoryWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.ScoreWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.StreakWidgetProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainActivity extends BaseActivity
|
||||
implements ListHabitsFragment.OnHabitClickListener
|
||||
{
|
||||
private ListHabitsFragment listHabitsFragment;
|
||||
private SharedPreferences prefs;
|
||||
private BroadcastReceiver receiver;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
|
||||
|
||||
public static final int RESULT_IMPORT_DATA = 1;
|
||||
public static final int RESULT_EXPORT_CSV = 2;
|
||||
public static final int RESULT_EXPORT_DB = 3;
|
||||
public static final int RESULT_BUG_REPORT = 4;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.list_habits_activity);
|
||||
|
||||
setupSupportActionBar(false);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
listHabitsFragment =
|
||||
(ListHabitsFragment) getSupportFragmentManager().findFragmentById(R.id.fragment1);
|
||||
|
||||
receiver = new Receiver();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
onPreLollipopStartup();
|
||||
|
||||
onStartup();
|
||||
}
|
||||
|
||||
private void onPreLollipopStartup()
|
||||
{
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
if(UIHelper.isNightMode()) return;
|
||||
|
||||
int color = getResources().getColor(R.color.grey_900);
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(color));
|
||||
}
|
||||
|
||||
private void onStartup()
|
||||
{
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
UIHelper.incrementLaunchCount(this);
|
||||
UIHelper.updateLastAppVersion(this);
|
||||
showTutorial();
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
ReminderHelper.createReminderAlarms(MainActivity.this);
|
||||
updateWidgets(MainActivity.this);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void showTutorial()
|
||||
{
|
||||
Boolean firstRun = prefs.getBoolean("pref_first_run", true);
|
||||
|
||||
if (firstRun)
|
||||
{
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean("pref_first_run", false);
|
||||
editor.putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply();
|
||||
editor.apply();
|
||||
|
||||
Intent intent = new Intent(this, IntroActivity.class);
|
||||
this.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
menu.clear();
|
||||
getMenuInflater().inflate(R.menu.list_habits_menu, menu);
|
||||
|
||||
MenuItem nightModeItem = menu.findItem(R.id.action_night_mode);
|
||||
nightModeItem.setChecked(UIHelper.isNightMode());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_night_mode:
|
||||
{
|
||||
if(UIHelper.isNightMode())
|
||||
UIHelper.setCurrentTheme(UIHelper.THEME_LIGHT);
|
||||
else
|
||||
UIHelper.setCurrentTheme(UIHelper.THEME_DARK);
|
||||
|
||||
refreshTheme();
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_settings:
|
||||
{
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivityForResult(intent, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_about:
|
||||
{
|
||||
Intent intent = new Intent(this, AboutActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_faq:
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(getString(R.string.helpURL)));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshTheme()
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Intent intent = new Intent(MainActivity.this, MainActivity.class);
|
||||
|
||||
MainActivity.this.finish();
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
startActivity(intent);
|
||||
|
||||
}
|
||||
}, 500); // Let the menu disappear first
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (resultCode)
|
||||
{
|
||||
case RESULT_IMPORT_DATA:
|
||||
listHabitsFragment.showImportDialog();
|
||||
break;
|
||||
|
||||
case RESULT_EXPORT_CSV:
|
||||
listHabitsFragment.exportAllHabits();
|
||||
break;
|
||||
|
||||
case RESULT_EXPORT_DB:
|
||||
listHabitsFragment.exportDB();
|
||||
break;
|
||||
|
||||
case RESULT_BUG_REPORT:
|
||||
generateBugReport();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void generateBugReport()
|
||||
{
|
||||
try
|
||||
{
|
||||
HabitsApplication.dumpBugReportToFile();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String log = "---------- BUG REPORT BEGINS ----------\n";
|
||||
log += HabitsApplication.generateBugReport();
|
||||
log += "---------- BUG REPORT ENDS ------------\n";
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("message/rfc822");
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { "dev@loophabits.org" });
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, "Bug Report - Loop Habit Tracker");
|
||||
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||
startActivity(intent);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
showToast(R.string.bug_report_failed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHabitClicked(Habit habit)
|
||||
{
|
||||
Intent intent = new Intent(this, ShowHabitActivity.class);
|
||||
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
listHabitsFragment.onPostExecuteCommand(refreshKey);
|
||||
|
||||
new BaseTask()
|
||||
{
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
dismissNotifications(MainActivity.this);
|
||||
updateWidgets(MainActivity.this);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void dismissNotifications(Context context)
|
||||
{
|
||||
for(Habit h : Habit.getHabitsWithReminder())
|
||||
{
|
||||
if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED)
|
||||
HabitBroadcastReceiver.dismissNotification(context, h);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context)
|
||||
{
|
||||
updateWidgets(context, CheckmarkWidgetProvider.class);
|
||||
updateWidgets(context, HistoryWidgetProvider.class);
|
||||
updateWidgets(context, ScoreWidgetProvider.class);
|
||||
updateWidgets(context, StreakWidgetProvider.class);
|
||||
updateWidgets(context, FrequencyWidgetProvider.class);
|
||||
}
|
||||
|
||||
private static void updateWidgets(Context context, Class providerClass)
|
||||
{
|
||||
ComponentName provider = new ComponentName(context, providerClass);
|
||||
Intent intent = new Intent(context, providerClass);
|
||||
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
|
||||
int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
localBroadcastManager.unregisterReceiver(receiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
class Receiver extends BroadcastReceiver
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
listHabitsFragment.onPostExecuteCommand(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults)
|
||||
{
|
||||
if (grantResults.length <= 0) return;
|
||||
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return;
|
||||
|
||||
listHabitsFragment.showImportDialog();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class ShowHabitActivity extends BaseActivity
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Uri data = getIntent().getData();
|
||||
habit = Habit.get(ContentUris.parseId(data));
|
||||
|
||||
setContentView(R.layout.show_habit_activity);
|
||||
|
||||
setupSupportActionBar(true);
|
||||
setupHabitActionBar();
|
||||
}
|
||||
|
||||
private void setupHabitActionBar()
|
||||
{
|
||||
if(habit == null) return;
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if(actionBar == null) return;
|
||||
|
||||
actionBar.setTitle(habit.name);
|
||||
|
||||
setupActionBarColor(ColorHelper.getColor(this, habit.color));
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChangeHabitColorCommand extends Command
|
||||
{
|
||||
List<Habit> habits;
|
||||
List<Integer> originalColors;
|
||||
Integer newColor;
|
||||
|
||||
public ChangeHabitColorCommand(List<Habit> habits, Integer newColor)
|
||||
{
|
||||
this.habits = habits;
|
||||
this.newColor = newColor;
|
||||
this.originalColors = new ArrayList<>(habits.size());
|
||||
|
||||
for(Habit h : habits)
|
||||
originalColors.add(h.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit.setColor(habits, newColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
|
||||
{
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
int k = 0;
|
||||
for(Habit h : habits)
|
||||
{
|
||||
h.color = originalColors.get(k++);
|
||||
h.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class CreateHabitCommand extends Command
|
||||
{
|
||||
private Habit model;
|
||||
private Long savedId;
|
||||
|
||||
public CreateHabitCommand(Habit model)
|
||||
{
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit savedHabit = new Habit(model);
|
||||
if (savedId == null)
|
||||
{
|
||||
savedHabit.save();
|
||||
savedId = savedHabit.getId();
|
||||
}
|
||||
else
|
||||
{
|
||||
savedHabit.save(savedId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
if(habit == null) throw new RuntimeException("Habit not found");
|
||||
|
||||
habit.cascadeDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeleteHabitsCommand extends Command
|
||||
{
|
||||
private List<Habit> habits;
|
||||
|
||||
public DeleteHabitsCommand(List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.cascadeDelete();
|
||||
|
||||
Habit.rebuildOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_restored;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class EditHabitCommand extends Command
|
||||
{
|
||||
private Habit original;
|
||||
private Habit modified;
|
||||
private long savedId;
|
||||
private boolean hasIntervalChanged;
|
||||
|
||||
public EditHabitCommand(Habit original, Habit modified)
|
||||
{
|
||||
this.savedId = original.getId();
|
||||
this.modified = new Habit(modified);
|
||||
this.original = new Habit(original);
|
||||
|
||||
hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) ||
|
||||
!this.original.freqNum.equals(this.modified.freqNum));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
copyAttributes(this.modified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
copyAttributes(this.original);
|
||||
}
|
||||
|
||||
private void copyAttributes(Habit model)
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
if(habit == null) throw new RuntimeException("Habit not found");
|
||||
|
||||
habit.copyAttributes(model);
|
||||
habit.save();
|
||||
|
||||
invalidateIfNeeded(habit);
|
||||
}
|
||||
|
||||
private void invalidateIfNeeded(Habit habit)
|
||||
{
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.checkmarks.deleteNewerThan(0);
|
||||
habit.streaks.deleteNewerThan(0);
|
||||
habit.scores.invalidateNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed_back;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UnarchiveHabitsCommand extends Command
|
||||
{
|
||||
|
||||
private List<Habit> habits;
|
||||
|
||||
public UnarchiveHabitsCommand(List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit.unarchive(habits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
Habit.archive(habits);
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_unarchived;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_archived;
|
||||
}
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
import com.android.datetimepicker.time.RadialPickerLayout;
|
||||
import com.android.datetimepicker.time.TimePickerDialog;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.UIHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EditHabitDialogFragment extends AppCompatDialogFragment
|
||||
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
|
||||
TimePickerDialog.OnTimeSetListener, Spinner.OnItemSelectedListener
|
||||
{
|
||||
private Integer mode;
|
||||
static final int EDIT_MODE = 0;
|
||||
static final int CREATE_MODE = 1;
|
||||
|
||||
private OnSavedListener onSavedListener;
|
||||
|
||||
private Habit originalHabit;
|
||||
private Habit modifiedHabit;
|
||||
|
||||
private TextView tvName;
|
||||
private TextView tvDescription;
|
||||
private TextView tvFreqNum;
|
||||
private TextView tvFreqDen;
|
||||
private TextView tvReminderTime;
|
||||
private TextView tvReminderDays;
|
||||
|
||||
private Spinner sFrequency;
|
||||
private ViewGroup llCustomFrequency;
|
||||
private ViewGroup llReminderDays;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private boolean is24HourMode;
|
||||
|
||||
public static EditHabitDialogFragment editSingleHabitFragment(long id)
|
||||
{
|
||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("habitId", id);
|
||||
args.putInt("editMode", EDIT_MODE);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
public static EditHabitDialogFragment createHabitFragment()
|
||||
{
|
||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("editMode", CREATE_MODE);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.edit_habit, container, false);
|
||||
tvName = (TextView) view.findViewById(R.id.input_name);
|
||||
tvDescription = (TextView) view.findViewById(R.id.input_description);
|
||||
tvFreqNum = (TextView) view.findViewById(R.id.input_freq_num);
|
||||
tvFreqDen = (TextView) view.findViewById(R.id.input_freq_den);
|
||||
tvReminderTime = (TextView) view.findViewById(R.id.inputReminderTime);
|
||||
tvReminderDays = (TextView) view.findViewById(R.id.inputReminderDays);
|
||||
|
||||
sFrequency = (Spinner) view.findViewById(R.id.sFrequency);
|
||||
llCustomFrequency = (ViewGroup) view.findViewById(R.id.llCustomFrequency);
|
||||
llReminderDays = (ViewGroup) view.findViewById(R.id.llReminderDays);
|
||||
|
||||
Button buttonSave = (Button) view.findViewById(R.id.buttonSave);
|
||||
Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard);
|
||||
ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor);
|
||||
|
||||
buttonSave.setOnClickListener(this);
|
||||
buttonDiscard.setOnClickListener(this);
|
||||
tvReminderTime.setOnClickListener(this);
|
||||
tvReminderDays.setOnClickListener(this);
|
||||
buttonPickColor.setOnClickListener(this);
|
||||
sFrequency.setOnItemSelectedListener(this);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
||||
Bundle args = getArguments();
|
||||
mode = (Integer) args.get("editMode");
|
||||
|
||||
is24HourMode = DateFormat.is24HourFormat(getActivity());
|
||||
|
||||
if (mode == CREATE_MODE)
|
||||
{
|
||||
getDialog().setTitle(R.string.create_habit);
|
||||
modifiedHabit = new Habit();
|
||||
modifiedHabit.freqNum = 1;
|
||||
modifiedHabit.freqDen = 1;
|
||||
modifiedHabit.color = prefs.getInt("pref_default_habit_palette_color", modifiedHabit.color);
|
||||
}
|
||||
else if (mode == EDIT_MODE)
|
||||
{
|
||||
Long habitId = (Long) args.get("habitId");
|
||||
if(habitId == null) throw new IllegalArgumentException("habitId must be specified");
|
||||
|
||||
originalHabit = Habit.get(habitId);
|
||||
modifiedHabit = new Habit(originalHabit);
|
||||
|
||||
getDialog().setTitle(R.string.edit_habit);
|
||||
tvName.append(modifiedHabit.name);
|
||||
tvDescription.append(modifiedHabit.description);
|
||||
}
|
||||
|
||||
if(savedInstanceState != null)
|
||||
{
|
||||
modifiedHabit.color = savedInstanceState.getInt("color", modifiedHabit.color);
|
||||
modifiedHabit.reminderMin = savedInstanceState.getInt("reminderMin", -1);
|
||||
modifiedHabit.reminderHour = savedInstanceState.getInt("reminderHour", -1);
|
||||
modifiedHabit.reminderDays = savedInstanceState.getInt("reminderDays", -1);
|
||||
|
||||
if(modifiedHabit.reminderMin < 0)
|
||||
modifiedHabit.clearReminder();
|
||||
}
|
||||
|
||||
tvFreqNum.append(modifiedHabit.freqNum.toString());
|
||||
tvFreqDen.append(modifiedHabit.freqDen.toString());
|
||||
|
||||
changeColor(modifiedHabit.color);
|
||||
updateFrequency();
|
||||
updateReminder();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void changeColor(int paletteColor)
|
||||
{
|
||||
modifiedHabit.color = paletteColor;
|
||||
tvName.setTextColor(ColorHelper.getColor(getActivity(), paletteColor));
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt("pref_default_habit_palette_color", paletteColor);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void updateReminder()
|
||||
{
|
||||
if (modifiedHabit.hasReminder())
|
||||
{
|
||||
tvReminderTime.setText(DateHelper.formatTime(getActivity(), modifiedHabit.reminderHour,
|
||||
modifiedHabit.reminderMin));
|
||||
llReminderDays.setVisibility(View.VISIBLE);
|
||||
|
||||
boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays);
|
||||
tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays));
|
||||
}
|
||||
else
|
||||
{
|
||||
tvReminderTime.setText(R.string.reminder_off);
|
||||
llReminderDays.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnSavedListener(OnSavedListener onSavedListener)
|
||||
{
|
||||
this.onSavedListener = onSavedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
switch(v.getId())
|
||||
{
|
||||
case R.id.inputReminderTime:
|
||||
onDateSpinnerClick();
|
||||
break;
|
||||
|
||||
case R.id.inputReminderDays:
|
||||
onWeekdayClick();
|
||||
break;
|
||||
|
||||
case R.id.buttonSave:
|
||||
onSaveButtonClick();
|
||||
break;
|
||||
|
||||
case R.id.buttonDiscard:
|
||||
dismiss();
|
||||
break;
|
||||
|
||||
case R.id.buttonPickColor:
|
||||
onColorButtonClick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onColorButtonClick()
|
||||
{
|
||||
int originalAndroidColor = ColorHelper.getColor(getActivity(), modifiedHabit.color);
|
||||
|
||||
ColorPickerDialog picker = ColorPickerDialog.newInstance(
|
||||
R.string.color_picker_default_title, ColorHelper.getPalette(getActivity()),
|
||||
originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int androidColor)
|
||||
{
|
||||
int paletteColor = ColorHelper.colorToPaletteIndex(getActivity(), androidColor);
|
||||
changeColor(paletteColor);
|
||||
}
|
||||
});
|
||||
picker.show(getFragmentManager(), "picker");
|
||||
}
|
||||
|
||||
private void onSaveButtonClick()
|
||||
{
|
||||
modifiedHabit.name = tvName.getText().toString().trim();
|
||||
modifiedHabit.description = tvDescription.getText().toString().trim();
|
||||
String freqNum = tvFreqNum.getText().toString();
|
||||
String freqDen = tvFreqDen.getText().toString();
|
||||
if(!freqNum.isEmpty()) modifiedHabit.freqNum = Integer.parseInt(freqNum);
|
||||
if(!freqDen.isEmpty()) modifiedHabit.freqDen = Integer.parseInt(freqDen);
|
||||
|
||||
if (!validate()) return;
|
||||
|
||||
Command command = null;
|
||||
Habit savedHabit = null;
|
||||
|
||||
if (mode == EDIT_MODE)
|
||||
{
|
||||
command = new EditHabitCommand(originalHabit, modifiedHabit);
|
||||
savedHabit = originalHabit;
|
||||
}
|
||||
else if (mode == CREATE_MODE)
|
||||
{
|
||||
command = new CreateHabitCommand(modifiedHabit);
|
||||
}
|
||||
|
||||
if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit);
|
||||
|
||||
dismiss();
|
||||
}
|
||||
|
||||
private boolean validate()
|
||||
{
|
||||
Boolean valid = true;
|
||||
|
||||
if (modifiedHabit.name.length() == 0)
|
||||
{
|
||||
tvName.setError(getString(R.string.validation_name_should_not_be_blank));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (modifiedHabit.freqNum <= 0)
|
||||
{
|
||||
tvFreqNum.setError(getString(R.string.validation_number_should_be_positive));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (modifiedHabit.freqNum > modifiedHabit.freqDen)
|
||||
{
|
||||
tvFreqNum.setError(getString(R.string.validation_at_most_one_rep_per_day));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void onDateSpinnerClick()
|
||||
{
|
||||
int defaultHour = 8;
|
||||
int defaultMin = 0;
|
||||
|
||||
if (modifiedHabit.hasReminder())
|
||||
{
|
||||
defaultHour = modifiedHabit.reminderHour;
|
||||
defaultMin = modifiedHabit.reminderMin;
|
||||
}
|
||||
|
||||
TimePickerDialog timePicker =
|
||||
TimePickerDialog.newInstance(this, defaultHour, defaultMin, is24HourMode);
|
||||
timePicker.show(getFragmentManager(), "timePicker");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void onWeekdayClick()
|
||||
{
|
||||
if(!modifiedHabit.hasReminder()) return;
|
||||
|
||||
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
|
||||
dialog.setListener(this);
|
||||
dialog.setSelectedDays(DateHelper.unpackWeekdayList(modifiedHabit.reminderDays));
|
||||
dialog.show(getFragmentManager(), "weekdayPicker");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
|
||||
{
|
||||
modifiedHabit.reminderHour = hour;
|
||||
modifiedHabit.reminderMin = minute;
|
||||
modifiedHabit.reminderDays = DateHelper.ALL_WEEK_DAYS;
|
||||
updateReminder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeCleared(RadialPickerLayout view)
|
||||
{
|
||||
modifiedHabit.clearReminder();
|
||||
updateReminder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWeekdaysPicked(boolean[] selectedDays)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i = 0; i < 7; i++)
|
||||
if(selectedDays[i]) count++;
|
||||
|
||||
if(count == 0) Arrays.fill(selectedDays, true);
|
||||
|
||||
modifiedHabit.reminderDays = DateHelper.packWeekdayList(selectedDays);
|
||||
updateReminder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putInt("color", modifiedHabit.color);
|
||||
|
||||
if(modifiedHabit.hasReminder())
|
||||
{
|
||||
outState.putInt("reminderMin", modifiedHabit.reminderMin);
|
||||
outState.putInt("reminderHour", modifiedHabit.reminderHour);
|
||||
outState.putInt("reminderDays", modifiedHabit.reminderDays);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if(parent.getId() == R.id.sFrequency)
|
||||
{
|
||||
switch (position)
|
||||
{
|
||||
case 0:
|
||||
modifiedHabit.freqNum = 1;
|
||||
modifiedHabit.freqDen = 1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
modifiedHabit.freqNum = 1;
|
||||
modifiedHabit.freqDen = 7;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
modifiedHabit.freqNum = 2;
|
||||
modifiedHabit.freqDen = 7;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
modifiedHabit.freqNum = 5;
|
||||
modifiedHabit.freqDen = 7;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
modifiedHabit.freqNum = 3;
|
||||
modifiedHabit.freqDen = 7;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateFrequency();
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void updateFrequency()
|
||||
{
|
||||
int quickSelectPosition = -1;
|
||||
|
||||
if(modifiedHabit.freqNum.equals(modifiedHabit.freqDen))
|
||||
quickSelectPosition = 0;
|
||||
|
||||
else if(modifiedHabit.freqNum == 1 && modifiedHabit.freqDen == 7)
|
||||
quickSelectPosition = 1;
|
||||
|
||||
else if(modifiedHabit.freqNum == 2 && modifiedHabit.freqDen == 7)
|
||||
quickSelectPosition = 2;
|
||||
|
||||
else if(modifiedHabit.freqNum == 5 && modifiedHabit.freqDen == 7)
|
||||
quickSelectPosition = 3;
|
||||
|
||||
if(quickSelectPosition >= 0)
|
||||
{
|
||||
sFrequency.setVisibility(View.VISIBLE);
|
||||
sFrequency.setSelection(quickSelectPosition);
|
||||
llCustomFrequency.setVisibility(View.GONE);
|
||||
tvFreqNum.setText(modifiedHabit.freqNum.toString());
|
||||
tvFreqDen.setText(modifiedHabit.freqDen.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
sFrequency.setVisibility(View.GONE);
|
||||
llCustomFrequency.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FilePickerDialog implements AdapterView.OnItemClickListener
|
||||
{
|
||||
private static final String PARENT_DIR = "..";
|
||||
|
||||
private final Activity activity;
|
||||
private ListView list;
|
||||
private Dialog dialog;
|
||||
private File currentPath;
|
||||
|
||||
public interface OnFileSelectedListener
|
||||
{
|
||||
void onFileSelected(File file);
|
||||
}
|
||||
|
||||
private OnFileSelectedListener listener;
|
||||
|
||||
public FilePickerDialog(Activity activity, File initialDirectory)
|
||||
{
|
||||
this.activity = activity;
|
||||
|
||||
list = new ListView(activity);
|
||||
list.setOnItemClickListener(this);
|
||||
|
||||
dialog = new Dialog(activity);
|
||||
dialog.setContentView(list);
|
||||
dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
|
||||
navigateTo(initialDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int which, long id)
|
||||
{
|
||||
String filename = (String) list.getItemAtPosition(which);
|
||||
File file;
|
||||
|
||||
if (filename.equals(PARENT_DIR))
|
||||
file = currentPath.getParentFile();
|
||||
else
|
||||
file = new File(currentPath, filename);
|
||||
|
||||
if (file.isDirectory())
|
||||
{
|
||||
navigateTo(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (listener != null) listener.onFileSelected(file);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public void show()
|
||||
{
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void setListener(OnFileSelectedListener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void navigateTo(File path)
|
||||
{
|
||||
if (!path.exists()) return;
|
||||
|
||||
File[] dirs = path.listFiles(new ReadableDirFilter());
|
||||
File[] files = path.listFiles(new RegularReadableFileFilter());
|
||||
if(dirs == null || files == null) return;
|
||||
|
||||
this.currentPath = path;
|
||||
dialog.setTitle(currentPath.getPath());
|
||||
list.setAdapter(new FilePickerAdapter(getFileList(path, dirs, files)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] getFileList(File path, File[] dirs, File[] files)
|
||||
{
|
||||
int count = 0;
|
||||
int length = dirs.length + files.length;
|
||||
String[] fileList;
|
||||
|
||||
if (path.getParentFile() == null || !path.getParentFile().canRead())
|
||||
{
|
||||
fileList = new String[length];
|
||||
}
|
||||
else
|
||||
{
|
||||
fileList = new String[length + 1];
|
||||
fileList[count++] = PARENT_DIR;
|
||||
}
|
||||
|
||||
Arrays.sort(dirs);
|
||||
Arrays.sort(files);
|
||||
|
||||
for (File dir : dirs)
|
||||
fileList[count++] = dir.getName();
|
||||
|
||||
for (File file : files)
|
||||
fileList[count++] = file.getName();
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
private class FilePickerAdapter extends ArrayAdapter<String>
|
||||
{
|
||||
public FilePickerAdapter(@NonNull String[] fileList)
|
||||
{
|
||||
super(FilePickerDialog.this.activity, android.R.layout.simple_list_item_1, fileList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int pos, View view, ViewGroup parent)
|
||||
{
|
||||
view = super.getView(pos, view, parent);
|
||||
TextView tv = (TextView) view;
|
||||
tv.setSingleLine(true);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableDirFilter implements FileFilter
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File file)
|
||||
{
|
||||
return (file.isDirectory() && file.canRead());
|
||||
}
|
||||
}
|
||||
|
||||
private class RegularReadableFileFilter implements FileFilter
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File file)
|
||||
{
|
||||
return !file.isDirectory() && file.canRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.BaseTask;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
|
||||
public class HistoryEditorDialog extends AppCompatDialogFragment
|
||||
implements DialogInterface.OnClickListener
|
||||
{
|
||||
private Habit habit;
|
||||
private Listener listener;
|
||||
HabitHistoryView historyView;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
Context context = getActivity();
|
||||
historyView = new HabitHistoryView(context, null);
|
||||
|
||||
if(savedInstanceState != null)
|
||||
{
|
||||
long id = savedInstanceState.getLong("habit", -1);
|
||||
if(id > 0) this.habit = Habit.get(id);
|
||||
}
|
||||
|
||||
int padding = (int) getResources().getDimension(R.dimen.history_editor_padding);
|
||||
historyView.setPadding(padding, 0, padding, 0);
|
||||
historyView.setHabit(habit);
|
||||
historyView.setIsEditable(true);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.history)
|
||||
.setView(historyView)
|
||||
.setPositiveButton(android.R.string.ok, this);
|
||||
|
||||
refreshData();
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private void refreshData()
|
||||
{
|
||||
new BaseTask()
|
||||
{
|
||||
@Override
|
||||
protected void doInBackground()
|
||||
{
|
||||
historyView.refreshData();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
int maxHeight = getResources().getDimensionPixelSize(R.dimen.history_editor_max_height);
|
||||
int width = metrics.widthPixels;
|
||||
int height = Math.min(metrics.heightPixels, maxHeight);
|
||||
|
||||
getDialog().getWindow().setLayout(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
if(historyView != null) historyView.setHabit(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
if(listener != null) listener.onHistoryEditorClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
outState.putLong("habit", habit.getId());
|
||||
}
|
||||
|
||||
public void setListener(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onHistoryEditorClosed();
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatDialogFragment;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
|
||||
public class WeekdayPickerDialog extends AppCompatDialogFragment
|
||||
implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener
|
||||
{
|
||||
|
||||
public interface OnWeekdaysPickedListener
|
||||
{
|
||||
void onWeekdaysPicked(boolean[] selectedDays);
|
||||
}
|
||||
|
||||
private boolean[] selectedDays;
|
||||
private OnWeekdaysPickedListener listener;
|
||||
|
||||
public void setListener(OnWeekdaysPickedListener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setSelectedDays(boolean[] selectedDays)
|
||||
{
|
||||
this.selectedDays = selectedDays;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle(R.string.select_weekdays)
|
||||
.setMultiChoiceItems(DateHelper.getLongDayNames(), selectedDays, this)
|
||||
.setPositiveButton(android.R.string.yes, this)
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked)
|
||||
{
|
||||
selectedDays[which] = isChecked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
if(listener != null) listener.onWeekdaysPicked(selectedDays);
|
||||
}
|
||||
}
|
||||