Compare commits
1816 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 | |||
| c2dd26eeb3 | |||
| 5831340343 | |||
| d7f6f52a49 | |||
| 93b442332d | |||
| e248824bcd | |||
| ec4a381d70 | |||
| b13f2b4228 | |||
| efc7b2cebb | |||
| add08d6054 | |||
| 5d8a348aaf | |||
| ec0e8ac24c | |||
| fee3137a6f | |||
| abe6b10964 | |||
| 6484b96e5a | |||
| 3d3d5b9b96 | |||
| 14364901ff | |||
| 440706882b | |||
| ca9d56e59e | |||
| 9a6dafaa79 | |||
| 9a44774284 | |||
| 2b23b36e36 | |||
| 78d4f86cab | |||
| 18e8390aed | |||
| cabcd5b1bf | |||
| 7e8a2a0c1c | |||
| 650971bf36 | |||
| 00774368d4 | |||
| cbf1bd3e19 | |||
| 4061921b93 | |||
| 59d42fe62f | |||
| 3ffa079e24 | |||
| bb950d61fc | |||
| 0ad0e5cf36 | |||
| 3b56d6d596 | |||
| 8d57273987 | |||
| b98853ab26 | |||
| 071cad73d4 | |||
| 16dc9c25d2 | |||
| 6b23858136 | |||
| e0198e9926 | |||
| 6445bf62bc | |||
| 83ef8564e1 | |||
| 88e8aad0d8 | |||
| a4b6728721 | |||
| e6e80b9841 | |||
| 442c3ed78d | |||
| ca9745f550 | |||
| 33bc8f78e7 | |||
| 0f223f8504 | |||
| 007996c69e | |||
| ff9d50b32a | |||
| 38024d71ce | |||
| 9ec1afc208 | |||
| e0888a9b4d | |||
| 8aade2f145 | |||
| 1ba1d775f7 | |||
| 605593d739 | |||
| bfbfcbd8cf | |||
| bd6cba8303 | |||
| befc3a0ad8 | |||
| f4c963e2c1 | |||
| e9a4a047c1 | |||
| 7ce1988d2e | |||
| 53911fa410 | |||
| f5f43d9a16 | |||
| 2399dccddc | |||
| dd5f37290c | |||
| dbf569ff87 | |||
| d0e475ad78 | |||
| b3199cf092 | |||
| 73e6f2a2d4 | |||
| 5b402478e9 | |||
| e3b7e9f60f | |||
| b0040bd83c | |||
| 56e1268f85 | |||
| 41d9e2f0f5 | |||
| 1fcfb9b22e | |||
| 2c80544aaa | |||
| 097a5be864 | |||
| 0287473e61 | |||
| f403dc6f77 | |||
| 23466523df | |||
| 83ca136346 | |||
| 7830b599db | |||
| 49376d4f41 | |||
| 8ee9c9c0b1 | |||
| ee9da29422 | |||
| ae7b0408b2 | |||
| d926d9dbd6 | |||
| 5694da2413 | |||
| 7e0ae144b8 | |||
| 1426d887e7 | |||
| acb8e820fd | |||
| eae0d66f51 | |||
| e933cbbc43 | |||
| 0b95b6a78c | |||
| 5e873a3659 | |||
| d292ecd988 | |||
| 19b484c368 | |||
| 834ae92d87 | |||
| 26ce92d381 | |||
| 0eb12812d4 | |||
| a1a636c718 | |||
| b2811b9797 | |||
| 4db7a6e89c | |||
| 677a643e5b | |||
| dbca3238f6 | |||
| ae5dd700f3 | |||
| 7e4c508d7d | |||
| d0c056eeaa | |||
| f172c69eed | |||
| 0cd4a41438 | |||
| d77c78249c | |||
| d9d48ff984 | |||
| 7be71ba4f8 | |||
| f0534fefbc | |||
| 30ef75bb45 | |||
| 44ed74a693 | |||
| 4aea527e07 | |||
| 638539259a | |||
| 75dec88411 | |||
| 2dc4fcbc46 | |||
| 7977d5247c | |||
| 8b18e32a16 | |||
| dbcad9a5f4 | |||
| 3bba75ff50 | |||
| 9bbeee66e2 | |||
| c6e76f4cfd | |||
| 980db1d171 | |||
| aee5d975db | |||
| e2bb4371d3 | |||
| fcee8552f0 | |||
| a4864e4612 | |||
| 8ddf4574e3 | |||
| f1ca8c5449 | |||
| 81a188f125 | |||
| 44402bf3a4 | |||
| 7f159149ef | |||
| 0c1e8d5131 | |||
| a4e3f7e037 | |||
| b354a0765b | |||
| bbd959dfda | |||
| d05d404c55 | |||
| 8938b0c9a6 | |||
| bd6fcd066c | |||
| 6a5f2abb76 | |||
| 33b5215b00 | |||
| 5d808dd2e8 | |||
| 767ec1b6de | |||
| 9e5f3d8f58 | |||
| 775d028629 | |||
| 4bfb839370 | |||
| 8f64b696d8 | |||
| c4bf31e778 | |||
| c757ce6548 | |||
| 78994bb3c4 | |||
| 2373ba5407 | |||
| b601a643dd | |||
| 90e513e778 | |||
| 728c9557f0 | |||
| 5bd0c842f5 | |||
| 97711087f9 | |||
| 2fe2d8834a | |||
| 93a893d660 | |||
| feff9a18e6 | |||
| b42565b770 | |||
| 3de702ced2 | |||
| 42f7f4042d | |||
| 2115a590f2 | |||
| f5f3ec9f88 | |||
| 6d5a8f5753 | |||
| 06fc04092b | |||
|
|
24c02605d1 | ||
| 11fcb67624 | |||
| abc92e390a | |||
| 52c07660b1 | |||
| 35f778c376 | |||
| 436ae7e574 | |||
| 5c683a77c2 | |||
| dd1f6c9efc | |||
| cba089407b | |||
| a7b0395a2a | |||
| 7d2946360f | |||
| 04e8432522 | |||
| 13d34945b4 | |||
| 756578508e | |||
| 38fc7650b9 | |||
| cf06ec0a4b | |||
| f0701f7b35 | |||
| de8018af7d | |||
| ac885e1503 | |||
| f85e09288c | |||
| 6f11596bb7 | |||
| 64c4367706 | |||
| c08702341a | |||
| abb8c09bc9 | |||
| 87255ceb25 | |||
| aedbfded0f | |||
| 779c0040bd | |||
| 7bba6a887f | |||
| 52a1d19793 | |||
| 35e346e7ea | |||
| 3102158bdb | |||
| 7afa65a53c | |||
| 2e5533d480 | |||
| b29c7e695a | |||
| 7df745eb82 | |||
| c0d518ca4d | |||
| eeff14a2ca | |||
| af3b18f518 | |||
| 3811f4b339 | |||
| 50a8aece6f | |||
| 2f66cfc417 | |||
| 132ce36a57 | |||
| 147f010d1b | |||
| afc91ed89a | |||
| 684c37d167 | |||
| 8b296ce2ae | |||
| e2e9e2e27a | |||
| 8b2f31c44a | |||
| 83db9fe2f2 | |||
| 9891139b5a | |||
| cbc8cbfea3 | |||
| 96c46b655d | |||
| 0de52d4fa3 | |||
| 16d85dc4c7 | |||
| c51b1fd399 | |||
| eb8dd1d450 | |||
| 357646152f | |||
| ae5be202b2 | |||
| cc3e2153d0 | |||
| 7433a2413d | |||
| 1cd8eb6849 | |||
| 7f009e2365 | |||
| 4cec7d7367 | |||
| f59c0bd7ff | |||
| 2d2bb8b4ed | |||
| e476096ae1 | |||
| e2c814a982 | |||
| 4fcaa68baf | |||
| 02e45dbe27 | |||
| bf6562f854 | |||
| 3c927e009a | |||
| a9bcae0f2f | |||
| bc54f3f916 | |||
| 5763d76167 | |||
| 86b66b1388 | |||
| 5e0422848b | |||
| a67a3297b1 | |||
| b2f2d4ad28 | |||
| 4dc8201dcb | |||
| f24e300b40 | |||
| 21157eaa19 | |||
| 0ea2c8e5c8 | |||
| 826491bc90 | |||
| 72f5ca5531 | |||
| f9da90a93a | |||
| 96c6a97ad0 | |||
| 20469789ea | |||
| abafffcff1 | |||
| d45a4445cc | |||
| 94f56a8869 | |||
| bc331d0a4d | |||
| 2ba7246e5f | |||
| 67b88c5012 | |||
| e0b637e84d | |||
| e14d73cf32 | |||
| 16b9584868 | |||
| c091116105 | |||
| 37e1a6233c | |||
| 6838a0f2d3 | |||
| 53e737c169 | |||
| 285f79ed95 | |||
| 4f65c8bef1 | |||
| 51b7cfc8fa | |||
| 55ed2585ea | |||
| 2c62e1578a | |||
| e3d96c0431 | |||
| b4c2d2237a | |||
| d14fbbd130 | |||
| e2c99d745e | |||
| dfe41176bc | |||
| 1ef4f8d456 | |||
| 6c810ee7a3 | |||
| 5115379fdd | |||
| d176ea91fb | |||
| 62df660079 | |||
| 790beb185a | |||
| 59c0af17d7 | |||
| 28dad560a6 | |||
| 9e410f8395 | |||
| 3656c51e95 | |||
| d3ebb4ff25 | |||
| 3c595bc79d | |||
| 1120f56dd4 | |||
| 2dfbcfcdb0 | |||
| 90a3964f0f | |||
| 6c05366f0f | |||
| dcc771abe7 | |||
| 743431ef67 | |||
| d5fc1a6886 | |||
| eeb0b109ae | |||
| ad391fa791 | |||
| e8bbae8ef9 | |||
| c9793df7c7 | |||
| 3c6114fc87 | |||
| 4ea4201b6a | |||
|
|
30b5b90091 | ||
| e6b7b8b590 | |||
| 2d675ed9b0 | |||
| 1db2f69f05 | |||
| 49a80faca3 | |||
| aa5df56437 | |||
| 581197be03 | |||
| dfe5c4954e | |||
| a0582b65d5 | |||
| 9edc3e12c9 | |||
| 7bb3db213f | |||
| e05a69b527 | |||
| d5df6ddcdb | |||
| 3016263750 | |||
| f9377e1768 | |||
| ae0dad9120 | |||
| be114bde7f | |||
| e7148abc2e | |||
| 2d87076a48 | |||
| 7da4ddf91b | |||
| e4e8d77acc | |||
| 7945a5faf9 | |||
| 3f7d25461d | |||
| 519737a1c3 | |||
| c3ff1fbe03 | |||
| d39e1978a2 | |||
| 536c095f23 | |||
| 8aaa5aca28 | |||
| 5540a66e08 | |||
| 01ffc6c144 | |||
| 21aa658acc | |||
| e17667d85d | |||
| f2948dac6f | |||
| 31a7d92f77 | |||
| 82d7970f86 | |||
| af1a21a959 | |||
| e7c1575083 | |||
| d5a14ca55b | |||
| c4d6b80944 | |||
| c4b6cad6bb | |||
| 7070530d0e | |||
| 1332a70eb4 | |||
| 11c9a3dab0 | |||
| 74f7b0fd28 | |||
| e693504183 | |||
| 7d9a94ae9e | |||
| 326cb8f73f | |||
| 6826bd1ddc | |||
| b5bc347624 | |||
| 55c058ff42 | |||
| e829bb8109 | |||
| 0921f9346e | |||
| eb017bf99b | |||
| 79b6ef8200 | |||
| 7ba62d6784 | |||
| f5e4a88415 | |||
| 075b7812eb | |||
| 3d42505fb9 | |||
| 824f98dd96 | |||
| fcb82bcb72 | |||
| 851cae3662 | |||
| 7778c5fb21 | |||
| ef847dac17 | |||
| 8102c18c67 | |||
| 59ed9ec9bd | |||
| ffdc923268 | |||
| 9232378d04 | |||
| b20fd44cbc | |||
| ded8800017 | |||
| 606f66b8d9 | |||
| 1bb6cd405b | |||
| babf7d64f0 | |||
| dfbcf78dd7 | |||
| e01c668e4d | |||
| 2eef696027 | |||
| ddd10cacd1 | |||
| 0cdde4901e | |||
| f13e9b7362 | |||
| 558f72d7c5 | |||
| 6e493f55bc | |||
| 5186ab840a | |||
| 65fd82d888 | |||
| 866b62987c | |||
| 18abb2038f | |||
| f7f4b5eeb0 | |||
| 45a7433773 | |||
| 40420c3a77 | |||
| 988b39f2e5 | |||
| a0803966f9 | |||
| e4af662836 | |||
| e95adab422 | |||
| 012fed01eb | |||
| 4cf2b8072b | |||
| 196a79a88c | |||
| 4b7e2e79a7 | |||
| 9156bba267 | |||
| 1a18bb939d | |||
| 144524e53b | |||
| 3d1c53396c | |||
| 1930db3cd1 | |||
| a2c2a5531a | |||
| eee2605f74 | |||
|
|
9df0c9ae9e | ||
| e0533505c1 | |||
|
|
433894336c | ||
| 9061182301 | |||
| 8c45f52d56 | |||
| 53c9dca3e2 | |||
| 8b37daa9fb | |||
| f9c2e83c79 | |||
| 71bdc70c1a | |||
| 5e4a40579a | |||
| d326be1224 | |||
| fb8a09c95c | |||
| 1635b9905d | |||
| 2d88fc0b20 | |||
| 1d74359c06 | |||
| f75f77cec7 | |||
| 8b10138cd6 | |||
| 2b40633110 | |||
| 1102d05a61 | |||
| 51e8c2f111 | |||
| 547e4e5f63 | |||
| 84d5c2aac6 | |||
| 2b3b423fa3 | |||
| 3b28c37c5e | |||
| e749e787ad | |||
| 9c5d582f24 | |||
| 75a4edb61c | |||
| 34c0758308 | |||
| 85963ae061 | |||
| e3390d5397 | |||
| 59a2f31a73 | |||
| c01f8450d3 | |||
| cea5241135 | |||
| 7784fc5c75 | |||
| 6dd017f33e | |||
| c8cd9f85f6 | |||
| d038bdb741 | |||
| f55e8d2c85 | |||
| f8dc1d9eae | |||
| 85393b0d40 | |||
| 75599ad20c | |||
| c6b948cbf5 | |||
| 4d42133a4b | |||
| 5b151805ff | |||
| aa86826bdb | |||
| 821373a340 | |||
| 8f37e293b1 | |||
| 0fb8ed0b53 | |||
| 0c696b2eb2 | |||
| cd127bc3f8 | |||
| 2cfc809490 | |||
| ba31dee16a | |||
| 146c743fb8 | |||
| 0c00e9ec2d | |||
| 49af55a2de | |||
| 0114b48197 | |||
| 09f615a5e6 | |||
| 9014acc548 | |||
| 4fb386be86 | |||
| ea8606be97 | |||
| e0527dc8ff | |||
| aaf2789a21 | |||
| f8dc64cc6b | |||
| ced5b751be | |||
| 8a60dda74e | |||
| c8c4df6ef7 | |||
| 0c0ac9dee5 | |||
| fdf6c91929 | |||
| 08d6e39a17 | |||
| b9bc7bd1b5 | |||
| 199d35d9c7 |
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 }}
|
||||
|
||||
57
.gitignore
vendored
@@ -1,35 +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/
|
||||
*.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
300
CHANGELOG.md
@@ -1,25 +1,289 @@
|
||||
# Changelog
|
||||
|
||||
### 1.2.0 (March 4, 2016)
|
||||
## [2.0.2] - 2021-05-23
|
||||
|
||||
* 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
|
||||
### Changed
|
||||
- Make checkmark widget resizable
|
||||
|
||||
### 1.1.1 (February 24, 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)
|
||||
|
||||
* 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
|
||||
## [2.0.1] - 2021-05-09
|
||||
|
||||
### 1.0.0 (February 19, 2016)
|
||||
### Added
|
||||
- Make midnight delay optional and disabled by default (@hiqua)
|
||||
- Add arrows to sort menu (@iSoron)
|
||||
|
||||
* Initial release
|
||||
### Removed
|
||||
- Temporarily remove experimental device sync functionality. This feature will be re-added in
|
||||
Loop 2.1.
|
||||
|
||||
### Changed
|
||||
- Make implicit checkmarks easier to read (@iSoron)
|
||||
- Update and improve list of translators (@hiqua, @iSoron)
|
||||
|
||||
### 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)
|
||||
|
||||
### 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)
|
||||
|
||||
## [2.0.0-alpha] - 2020-11-29
|
||||
|
||||
### 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)
|
||||
|
||||
### Removed
|
||||
- Drop support to devices older than Android 6.0 (API 23)
|
||||
|
||||
### 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)
|
||||
|
||||
### Refactoring
|
||||
- Convert files to Kotlin (@olegivo)
|
||||
|
||||
## [1.8.12] - 2021-01-30
|
||||
|
||||
- Fix bug that caused incorrect check marks to show after scrolling (#713)
|
||||
- Fix issue preventing widgets from updating at midnight (#680)
|
||||
|
||||
## [1.8.11] - 2020-12-29
|
||||
|
||||
- Fix theme issues on Xiaomi phones
|
||||
|
||||
## [1.8.10] - 2020-11-26
|
||||
|
||||
- Update translations
|
||||
|
||||
## [1.8.9] - 2020-11-18
|
||||
|
||||
- Manage exceptions when activities don't exist to handle intents (#181)
|
||||
- MemoryHabitList: Inherit parent's order (#598)
|
||||
- Remove notification groups; revert to default system behavior
|
||||
- Remove SyncManager and Internet permission
|
||||
|
||||
## [1.8.8] - 2020-06-21
|
||||
|
||||
- Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably.
|
||||
- Fix crash when saving habit
|
||||
|
||||
## [1.8.0] - 2020-01-01
|
||||
|
||||
- New bar chart showing number of repetitions performed in each week, month, quarter or year.
|
||||
- Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will no longer break your streak.
|
||||
- Many more colors to choose from (now 20 in total).
|
||||
- Ability to customize how transparent the widgets are on your home screen.
|
||||
- Ability to customize the first day of the week.
|
||||
- Yes/No buttons on notifications, instead of just "Check".
|
||||
- Automatic dark theme according to phone settings (Android 10).
|
||||
- Smaller APK and backup files.
|
||||
- Many other internal code changes improving performance and stability.
|
||||
|
||||
## [1.7.11] - 2019-08-10
|
||||
|
||||
- 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
|
||||
|
||||
207
NOTICE.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Copyright Notices
|
||||
|
||||
### ActiveAndroid
|
||||
|
||||
<https://github.com/pardom/ActiveAndroid>
|
||||
|
||||
Copyright (C) 2010 Michael Pardo
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Android Open Source Project
|
||||
|
||||
<https://source.android.com/>
|
||||
|
||||
Copyright (C) 2013 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### FontAwesome
|
||||
|
||||
<http://fontawesome.io>
|
||||
|
||||
Font Awesome is a full suite of 605 pictographic icons for easy scalable
|
||||
vector graphics on websites, created and maintained by Dave Gandy. Licensed
|
||||
under the SIL OFL 1.1.
|
||||
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
### Material Design Icons
|
||||
|
||||
<https://github.com/google/material-design-icons>
|
||||
|
||||
Material design icons are the official icon set from Google that are designed
|
||||
under the material design guidelines. Available under the Creative Common
|
||||
Attribution 4.0 International License (CC-BY 4.0).
|
||||
|
||||
### Android Flow Layout
|
||||
|
||||
<https://github.com/ApmeM/android-flowlayout>
|
||||
|
||||
Extended linear layout that wrap its content when there is no place in the current line.
|
||||
|
||||
Copyright 2011, Artem Votincev (apmem.org)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
use this file except in compliance with the License. You may obtain a copy
|
||||
of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
### Dagger 2
|
||||
|
||||
<https://github.com/google/dagger>
|
||||
|
||||
A fast dependency injector for Android and Java.
|
||||
|
||||
Copyright 2012 Square, Inc.
|
||||
Copyright 2012 Google, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### AutoFactory
|
||||
|
||||
<https://github.com/google/auto/tree/master/factory>
|
||||
|
||||
A source code generator for JSR-330-compatible factories.
|
||||
|
||||
Copyright 2013 Google, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Retrolambda
|
||||
|
||||
<https://github.com/orfjackal/retrolambda>
|
||||
|
||||
Backport of Java 8's lambda expressions to Java 7, 6 and 5
|
||||
|
||||
Copyright (c) 2013-2016 Esko Luontola and other Retrolambda contributors
|
||||
This software is released under the Apache License 2.0.
|
||||
The license text is at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
### PebbleKit SDK
|
||||
|
||||
<https://github.com/pebble/pebble-android-sdk/>
|
||||
|
||||
Android PebbleKit SDK to talk to the Pebble via Bluetooth
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2014 - 2015 Pebble Technology
|
||||
|
||||
### AppIntro
|
||||
|
||||
<https://github.com/PaoloRotolo/AppIntro>
|
||||
|
||||
Make a cool intro for your Android app.
|
||||
|
||||
Copyright 2015 Paolo Rotolo
|
||||
Copyright 2016 Maximilian Narr
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### ButterKnife
|
||||
|
||||
<https://github.com/JakeWharton/butterknife>
|
||||
|
||||
Bind Android views and callbacks to fields and methods
|
||||
|
||||
Copyright 2013 Jake Wharton
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### opencsv
|
||||
|
||||
<http://opencsv.sourceforge.net/>
|
||||
|
||||
Opencsv is a very simple csv (comma-separated values) parser library for Java.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
156
README.md
@@ -1,31 +1,25 @@
|
||||
# Loop Habit Tracker
|
||||
<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, 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.
|
||||
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.
|
||||
|
||||
<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" width="200px"/></a>
|
||||
|
||||
## Features
|
||||
|
||||
<b>Simple, beautiful and modern interface</b>
|
||||
Loop has a minimalistic interface that is easy to use and follows the material design guidelines.
|
||||
|
||||
<b>Habit score</b>
|
||||
In addition to showing your current streak, Loop has an advanced algorithm for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress.
|
||||
|
||||
<b>Detailed graphs and statistics</b>
|
||||
Clearly see how your habits improved over time with beautiful and detailed graphs. Scroll back to see the complete history of your habits.
|
||||
|
||||
<b>Flexible schedules</b>
|
||||
Supports both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day.
|
||||
|
||||
<b>Reminders</b>
|
||||
Create an individual reminder for each habit, at a chosen hour of the day. Easily check, dismiss or snooze your habit directly from the notification, without opening the app.
|
||||
|
||||
<b>Optimized for smartwatches</b>
|
||||
Reminders can be checked, snoozed or dismissed directly from your Android Wear watch.
|
||||
|
||||
<b>Completely ad-free and open source</b>
|
||||
There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under the GPLv3.
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
</p>
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -34,14 +28,104 @@ There are absolutely no advertisements, annoying notifications or intrusive perm
|
||||
[![Habit strength][screen3th]][screen3]
|
||||
[![Habit history and streaks][screen4th]][screen4]
|
||||
[![Widgets][screen5th]][screen5]
|
||||
[![Night mode][screen6th]][screen6]
|
||||
|
||||
[screen1]: screenshots/original/uhabits1.png
|
||||
[screen2]: screenshots/original/uhabits2.png
|
||||
[screen3]: screenshots/original/uhabits3.png
|
||||
[screen4]: screenshots/original/uhabits4.png
|
||||
[screen5]: screenshots/original/uhabits5.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
|
||||
## Features
|
||||
|
||||
* <b>Beautiful, minimalistic and lightweight interface.</b>
|
||||
Loop has an elegant and minimalistic interface that is very easy to use, even for first-time users. Highly optimized for speed, the app works well even on older phones.
|
||||
|
||||
* <b>Habit score.</b>
|
||||
Loop has an advanced formula for calculating the strength of your habits. Every repetition makes your habit stronger and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your progress, unlike many other don't-break-the-chain apps.
|
||||
|
||||
* <b>Flexible schedules.</b>
|
||||
In addition to daily habits, Loop supports habits with more complex schedules, such as 3 times per week or every other day.
|
||||
|
||||
* <b>Reminders.</b>
|
||||
Schedule notifications to remind you of your habits. Each habit can have its own reminder, at a chosen time of the day. Easily check or dismiss your habit directly from the notification.
|
||||
|
||||
* <b>Widgets.</b>
|
||||
Be reminded of your habits whenever you unlock your phone. Colorful widgets allow you to track your habits directly from your home screen, without even opening the app.
|
||||
|
||||
* <b>Take control of your data.</b>
|
||||
If you want to further analyze your data, or move it to another service, Loop allows you to export it to spreadsheets (CSV) or to a database file (SQLite). For power users, checkmarks can be added through other apps, such as Tasker.
|
||||
|
||||
* <b>No limitations.</b>
|
||||
Track as many habits as you wish. Loop imposes no artificial limits on how many habits you can have. All features are available to all users. There are no in-app purchases.
|
||||
|
||||
* <b>Completely ad-free and open source.</b>
|
||||
There are no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The app is completely open-source (GPLv3).
|
||||
|
||||
* <b>Works offline and respects your privacy.</b>
|
||||
Loop doesn't require an Internet connection or online account registration. Your confidential data is never sent to anyone. Neither the developers nor any third-parties have access to it.
|
||||
|
||||
## Installing
|
||||
|
||||
The easiest way to install Loop is through the [Google Play Store][playstore] or [F-Droid][fdroid].
|
||||
You may also download and install the APK from the [releases page][releases];
|
||||
note, however, that the app will not be updated automatically. To build this
|
||||
app from the source code, see [build instructions][build].
|
||||
|
||||
## Contributing
|
||||
|
||||
Loop is an open source project developed entirely by volunteers. If you would
|
||||
like to contribute to the project, you are very welcome. There are many ways to
|
||||
contribute, even if you are not a software developer.
|
||||
|
||||
* **Report bugs, suggest features.** The easiest way to contribute is to simply
|
||||
use the app and let us know if you find any problems or have any suggestions
|
||||
to improve it. 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
|
||||
and colleagues. You can also rate and review the app on Google Play Store, to help
|
||||
other users find it more easily.
|
||||
|
||||
* **Translate the app into your own language.** If you are not a native English
|
||||
speaker, and would like to see the app translated into your own language,
|
||||
please join our [open translation project][poedit]. If the translation
|
||||
is already completed, you are also very welcome to join and proofread it.
|
||||
|
||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||
contribute with code. Please see `docs/GUIDELINES.md`.
|
||||
|
||||
## License
|
||||
|
||||
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||
|
||||
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/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
|
||||
[build]: https://github.com/iSoron/uhabits/blob/dev/docs/BUILD.md
|
||||
[beta]: https://play.google.com/apps/testing/org.isoron.uhabits
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "21.1.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.isoron.uhabits"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 23
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
compile 'com.github.paolorotolo:appintro:3.4.0'
|
||||
compile project(':libs:drag-sort-listview:library')
|
||||
compile files('libs/ActiveAndroid.jar')
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
</lint>
|
||||
@@ -1,131 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
package="org.isoron.uhabits"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="9"
|
||||
android:versionName="1.2.0">
|
||||
|
||||
<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" />
|
||||
|
||||
<application
|
||||
android:name="com.activeandroid.app.Application"
|
||||
android:allowBackup="true"
|
||||
android:backupAgent=".HabitsBackupAgent"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/main_activity_title"
|
||||
android:theme="@style/AppBaseTheme">
|
||||
|
||||
<meta-data
|
||||
android:name="AA_DB_NAME"
|
||||
android:value="uhabits.db"/>
|
||||
|
||||
<meta-data
|
||||
android:name="AA_DB_VERSION"
|
||||
android:value="12"/>
|
||||
|
||||
<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>
|
||||
|
||||
<receiver
|
||||
android:name=".HabitBroadcastReceiver"/>
|
||||
|
||||
<activity
|
||||
android:name=".ShowHabitActivity"
|
||||
android:label="@string/title_activity_show_habit"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.isoron.uhabits.MainActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<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"/>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.CheckmarkWidgetProvider"
|
||||
android:label="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="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="Score">
|
||||
<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="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>
|
||||
|
||||
<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>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1 +0,0 @@
|
||||
alter table habits add column reminder_days integer not null default 127;
|
||||
@@ -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,91 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
public class ColorHelper
|
||||
{
|
||||
public static final int[] palette =
|
||||
{
|
||||
Color.parseColor("#D32F2F"), // red
|
||||
Color.parseColor("#E64A19"), // orange
|
||||
Color.parseColor("#F9A825"), // yellow
|
||||
Color.parseColor("#AFB42B"), // light green
|
||||
Color.parseColor("#388E3C"), // dark green
|
||||
Color.parseColor("#00897B"), // teal
|
||||
Color.parseColor("#00ACC1"), // cyan
|
||||
Color.parseColor("#039BE5"), // blue
|
||||
Color.parseColor("#5E35B1"), // deep purple
|
||||
Color.parseColor("#8E24AA"), // purple
|
||||
Color.parseColor("#D81B60"), // pink
|
||||
Color.parseColor("#303030"), // dark grey
|
||||
Color.parseColor("#aaaaaa") // light grey
|
||||
};
|
||||
|
||||
public static int mixColors(int color1, int color2, float amount)
|
||||
{
|
||||
final byte ALPHA_CHANNEL = 24;
|
||||
final byte RED_CHANNEL = 16;
|
||||
final byte GREEN_CHANNEL = 8;
|
||||
final byte BLUE_CHANNEL = 0;
|
||||
|
||||
final float inverseAmount = 1.0f - amount;
|
||||
|
||||
int a = ((int) (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) +
|
||||
((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) & 0xff;
|
||||
int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) +
|
||||
((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff;
|
||||
int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) +
|
||||
((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff;
|
||||
int b = ((int) (((float) (color1 & 0xff) * amount) +
|
||||
((float) (color2 & 0xff) * inverseAmount))) & 0xff;
|
||||
|
||||
return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL;
|
||||
}
|
||||
|
||||
public static int setHue(int color, float newHue)
|
||||
{
|
||||
return setHSVParameter(color, newHue, 0);
|
||||
}
|
||||
|
||||
public static int setSaturation(int color, float newSaturation)
|
||||
{
|
||||
return setHSVParameter(color, newSaturation, 1);
|
||||
}
|
||||
|
||||
public static int setValue(int color, float newValue)
|
||||
{
|
||||
return setHSVParameter(color, newValue, 2);
|
||||
}
|
||||
|
||||
public static int setMinValue(int color, float newValue)
|
||||
{
|
||||
float hsv[] = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[2] = Math.max(hsv[2], newValue);
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
private static int setHSVParameter(int color, float newValue, int index)
|
||||
{
|
||||
float hsv[] = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[index] = newValue;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
public abstract class Command
|
||||
{
|
||||
public abstract void execute();
|
||||
|
||||
public abstract void undo();
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class DateHelper
|
||||
{
|
||||
public static int millisecondsInOneDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
public static long getLocalTime()
|
||||
{
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
long now = new Date().getTime();
|
||||
return now + tz.getOffset(now);
|
||||
}
|
||||
|
||||
public static long toLocalTime(long timestamp)
|
||||
{
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
long now = new Date(timestamp).getTime();
|
||||
return now + tz.getOffset(now);
|
||||
}
|
||||
|
||||
public static long getStartOfDay(long timestamp)
|
||||
{
|
||||
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
|
||||
}
|
||||
|
||||
public static GregorianCalendar getStartOfTodayCalendar()
|
||||
{
|
||||
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
|
||||
return day;
|
||||
}
|
||||
|
||||
public static GregorianCalendar getCalendar(long timestamp)
|
||||
{
|
||||
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
day.setTimeInMillis(timestamp);
|
||||
return day;
|
||||
}
|
||||
|
||||
public static int getWeekday(long timestamp)
|
||||
{
|
||||
GregorianCalendar day = getCalendar(timestamp);
|
||||
return day.get(GregorianCalendar.DAY_OF_WEEK) % 7;
|
||||
}
|
||||
|
||||
public static long getStartOfToday()
|
||||
{
|
||||
return getStartOfDay(DateHelper.getLocalTime());
|
||||
}
|
||||
|
||||
public static String formatTime(Context context, int hours, int minutes)
|
||||
{
|
||||
int reminderMilliseconds = (hours * 60 + minutes) * 60 * 1000;
|
||||
|
||||
Date date = new Date(reminderMilliseconds);
|
||||
java.text.DateFormat df = DateFormat.getTimeFormat(context);
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
public static String formatHeaderDate(GregorianCalendar day)
|
||||
{
|
||||
String dayOfMonth = Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH));
|
||||
String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
|
||||
GregorianCalendar.SHORT, Locale.getDefault());
|
||||
|
||||
return dayOfWeek + "\n" + dayOfMonth;
|
||||
}
|
||||
|
||||
public static int differenceInDays(Date from, Date to)
|
||||
{
|
||||
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
|
||||
return (int) (milliseconds / millisecondsInOneDay);
|
||||
}
|
||||
|
||||
public static String[] getShortDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.SHORT);
|
||||
}
|
||||
|
||||
public static String[] getLongDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.LONG);
|
||||
}
|
||||
|
||||
|
||||
public static String[] getDayNames(int format)
|
||||
{
|
||||
String[] wdays = new String[7];
|
||||
|
||||
GregorianCalendar day = new GregorianCalendar();
|
||||
day.set(GregorianCalendar.DAY_OF_WEEK, 0);
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return wdays;
|
||||
}
|
||||
|
||||
public static String formatWeekdayList(Context context, boolean weekday[])
|
||||
{
|
||||
String shortDayNames[] = getShortDayNames();
|
||||
String longDayNames[] = getLongDayNames();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
int count = 0;
|
||||
int first = 0;
|
||||
boolean isFirst = true;
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
if(weekday[i])
|
||||
{
|
||||
if(isFirst) first = i;
|
||||
else buffer.append(", ");
|
||||
|
||||
buffer.append(shortDayNames[i]);
|
||||
isFirst = false;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(count == 1) return longDayNames[first];
|
||||
if(count == 2 && weekday[0] && weekday[1]) return context.getString(R.string.weekends);
|
||||
if(count == 5 && !weekday[0] && !weekday[1]) return context.getString(R.string.any_weekday);
|
||||
if(count == 7) return context.getString(R.string.any_day);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static Integer packWeekdayList(boolean weekday[])
|
||||
{
|
||||
int list = 0;
|
||||
int current = 1;
|
||||
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
if(weekday[i]) list |= current;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean[] unpackWeekdayList(int list)
|
||||
{
|
||||
boolean[] weekday = new boolean[7];
|
||||
int current = 1;
|
||||
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
if((list & current) != 0) weekday[i] = true;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return weekday;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.isoron.uhabits.BuildConfig;
|
||||
|
||||
public abstract class DialogHelper
|
||||
{
|
||||
|
||||
public static final String ISORON_NAMESPACE = "http://isoron.org/android";
|
||||
private static Typeface fontawesome;
|
||||
|
||||
public interface OnSavedListener
|
||||
{
|
||||
void onSaved(Command command, Object savedObject);
|
||||
}
|
||||
|
||||
public static void showSoftKeyboard(View view)
|
||||
{
|
||||
InputMethodManager imm = (InputMethodManager) view.getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
public static void vibrate(Context context, int duration)
|
||||
{
|
||||
Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
vb.vibrate(duration);
|
||||
}
|
||||
|
||||
|
||||
public static void incrementLaunchCount(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
int count = prefs.getInt("launch_count", 0);
|
||||
prefs.edit().putInt("launch_count", count + 1).apply();
|
||||
}
|
||||
|
||||
public static void updateLastAppVersion(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply();
|
||||
}
|
||||
|
||||
public static int getLaunchCount(Context context)
|
||||
{
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getInt("launch_count", 0);
|
||||
}
|
||||
|
||||
public static String getAttribute(Context context, AttributeSet attrs, String name)
|
||||
{
|
||||
int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0);
|
||||
|
||||
if(resId != 0)
|
||||
return context.getResources().getString(resId);
|
||||
else
|
||||
return attrs.getAttributeValue(ISORON_NAMESPACE, name);
|
||||
}
|
||||
|
||||
public static float dpToPixels(Context context, float dp)
|
||||
{
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.helpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
abstract public class ReplayableActivity extends Activity
|
||||
{
|
||||
private static int MAX_UNDO_LEVEL = 15;
|
||||
|
||||
private LinkedList<Command> undoList;
|
||||
private LinkedList<Command> redoList;
|
||||
private Toast toast;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
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)
|
||||
{
|
||||
ReplayableActivity.this.onPostExecuteCommand(refreshKey);
|
||||
BackupManager.dataChanged("org.isoron.uhabits");
|
||||
}
|
||||
}.execute();
|
||||
|
||||
|
||||
showToast(command.getExecuteStringId());
|
||||
}
|
||||
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||
{
|
||||
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
|
||||
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
|
||||
public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
|
||||
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent)
|
||||
{
|
||||
switch (intent.getAction())
|
||||
{
|
||||
case ACTION_SHOW_REMINDER:
|
||||
createNotification(context, intent);
|
||||
createReminderAlarms(context);
|
||||
break;
|
||||
|
||||
case ACTION_DISMISS:
|
||||
dismissAllHabits();
|
||||
break;
|
||||
|
||||
case ACTION_CHECK:
|
||||
checkHabit(context, intent);
|
||||
break;
|
||||
|
||||
case ACTION_SNOOZE:
|
||||
snoozeHabit(context, intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createReminderAlarms(final Context context)
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ReminderHelper.createReminderAlarms(context);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private void snoozeHabit(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
|
||||
|
||||
Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
ReminderHelper.createReminderAlarm(context, habit,
|
||||
new Date().getTime() + delayMinutes * 60 * 1000);
|
||||
dismissNotification(context, habit);
|
||||
}
|
||||
|
||||
private void checkHabit(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
|
||||
Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
habit.repetitions.toggle(timestamp);
|
||||
habit.save();
|
||||
dismissNotification(context, habit);
|
||||
|
||||
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
|
||||
Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH);
|
||||
manager.sendBroadcast(refreshIntent);
|
||||
|
||||
MainActivity.updateWidgets(context);
|
||||
}
|
||||
|
||||
private void dismissAllHabits()
|
||||
{
|
||||
for (Habit h : Habit.getHighlightedHabits())
|
||||
{
|
||||
h.highlight = 0;
|
||||
h.save();
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissNotification(Context context, Habit habit)
|
||||
{
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
|
||||
|
||||
private void createNotification(Context context, Intent intent)
|
||||
{
|
||||
Uri data = intent.getData();
|
||||
Habit habit = Habit.get(ContentUris.parseId(data));
|
||||
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
|
||||
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
|
||||
|
||||
if (habit.repetitions.hasImplicitRepToday()) return;
|
||||
|
||||
habit.highlight = 1;
|
||||
habit.save();
|
||||
|
||||
if (!checkWeekday(intent, habit)) return;
|
||||
|
||||
// Check if reminder has been turned off after alarm was scheduled
|
||||
if (habit.reminderHour == null) return;
|
||||
|
||||
Intent contentIntent = new Intent(context, MainActivity.class);
|
||||
contentIntent.setData(data);
|
||||
PendingIntent contentPendingIntent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
|
||||
PendingIntent dismissPendingIntent = buildDismissIntent(context);
|
||||
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
|
||||
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
|
||||
|
||||
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender =
|
||||
new NotificationCompat.WearableExtender().setBackground(
|
||||
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
|
||||
|
||||
Notification notification =
|
||||
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(habit.name)
|
||||
.setContentText(habit.description)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.setDeleteIntent(dismissPendingIntent)
|
||||
.addAction(R.drawable.ic_action_check,
|
||||
context.getString(R.string.check), checkIntentPending)
|
||||
.addAction(R.drawable.ic_action_snooze,
|
||||
context.getString(R.string.snooze), snoozeIntentPending)
|
||||
.setSound(soundUri)
|
||||
.extend(wearableExtender)
|
||||
.setWhen(reminderTime)
|
||||
.setShowWhen(true)
|
||||
.build();
|
||||
|
||||
notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||
|
||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||
notificationManager.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
snoozeIntent.setData(data);
|
||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
||||
}
|
||||
|
||||
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
|
||||
{
|
||||
Uri data = habit.getUri();
|
||||
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
checkIntent.setData(data);
|
||||
checkIntent.setAction(ACTION_CHECK);
|
||||
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
||||
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
}
|
||||
|
||||
public static PendingIntent buildDismissIntent(Context context)
|
||||
{
|
||||
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
deleteIntent.setAction(ACTION_DISMISS);
|
||||
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.backup.BackupAgentHelper;
|
||||
import android.app.backup.FileBackupHelper;
|
||||
import android.app.backup.SharedPreferencesBackupHelper;
|
||||
|
||||
public class HabitsBackupAgent extends BackupAgentHelper
|
||||
{
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
addHelper("preferences", new SharedPreferencesBackupHelper(this, "preferences"));
|
||||
addHelper("database", new FileBackupHelper(this, "../databases/uhabits.db"));
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.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_3),
|
||||
getString(R.string.intro_description_3), R.drawable.intro_icon_3,
|
||||
Color.parseColor("#7cb342")));
|
||||
|
||||
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,177 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.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.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.helpers.ReplayableActivity;
|
||||
import org.isoron.uhabits.fragments.ListHabitsFragment;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.HistoryWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.ScoreWidgetProvider;
|
||||
import org.isoron.uhabits.widgets.StreakWidgetProvider;
|
||||
|
||||
public class MainActivity extends ReplayableActivity
|
||||
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";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.list_habits_activity);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
listHabitsFragment =
|
||||
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
|
||||
|
||||
receiver = new Receiver();
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
|
||||
|
||||
onStartup();
|
||||
}
|
||||
|
||||
private void onStartup()
|
||||
{
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
DialogHelper.incrementLaunchCount(this);
|
||||
DialogHelper.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)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.list_habits_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_settings:
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
updateWidgets(this);
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context)
|
||||
{
|
||||
updateWidgets(context, CheckmarkWidgetProvider.class);
|
||||
updateWidgets(context, HistoryWidgetProvider.class);
|
||||
updateWidgets(context, ScoreWidgetProvider.class);
|
||||
updateWidgets(context, StreakWidgetProvider.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.isoron.uhabits.fragments.SettingsFragment;
|
||||
|
||||
public class SettingsActivity extends Activity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.isoron.helpers.ReplayableActivity;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class ShowHabitActivity extends ReplayableActivity
|
||||
{
|
||||
|
||||
public Habit habit;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Uri data = getIntent().getData();
|
||||
habit = Habit.get(ContentUris.parseId(data));
|
||||
getActionBar().setTitle(habit.name);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color));
|
||||
}
|
||||
|
||||
setContentView(R.layout.show_habit_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
getMenuInflater().inflate(R.menu.show_habit_activity_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_settings:
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ArchiveHabitsCommand extends Command
|
||||
{
|
||||
|
||||
private List<Habit> habits;
|
||||
|
||||
public ArchiveHabitsCommand(Habit habit)
|
||||
{
|
||||
habits = new LinkedList<>();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
public ArchiveHabitsCommand(List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.archive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.unarchive();
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_archived;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_unarchived;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.R;
|
||||
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()
|
||||
{
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for(Habit h : habits)
|
||||
{
|
||||
h.color = newColor;
|
||||
h.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
int k = 0;
|
||||
for(Habit h : habits)
|
||||
{
|
||||
h.color = originalColors.get(k++);
|
||||
h.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
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.get(savedId).delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_deleted;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_restored;
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
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 != this.modified.freqDen ||
|
||||
this.original.freqNum != this.modified.freqNum);
|
||||
}
|
||||
|
||||
public void execute()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(modified);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.checkmarks.deleteNewerThan(0);
|
||||
habit.streaks.deleteNewerThan(0);
|
||||
habit.scores.deleteNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void undo()
|
||||
{
|
||||
Habit habit = Habit.get(savedId);
|
||||
habit.copyAttributes(original);
|
||||
habit.save();
|
||||
if (hasIntervalChanged)
|
||||
{
|
||||
habit.checkmarks.deleteNewerThan(0);
|
||||
habit.streaks.deleteNewerThan(0);
|
||||
habit.scores.deleteNewerThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_changed_back;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
private Long offset;
|
||||
private Habit habit;
|
||||
|
||||
public ToggleRepetitionCommand(Habit habit, long offset)
|
||||
{
|
||||
this.offset = offset;
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
habit.repetitions.toggle(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.commands;
|
||||
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class UnarchiveHabitsCommand extends Command
|
||||
{
|
||||
|
||||
private List<Habit> habits;
|
||||
|
||||
public UnarchiveHabitsCommand(Habit habit)
|
||||
{
|
||||
habits = new LinkedList<>();
|
||||
habits.add(habit);
|
||||
}
|
||||
|
||||
public UnarchiveHabitsCommand(List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.unarchive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for(Habit h : habits)
|
||||
h.archive();
|
||||
}
|
||||
|
||||
public Integer getExecuteStringId()
|
||||
{
|
||||
return R.string.toast_habit_unarchived;
|
||||
}
|
||||
|
||||
public Integer getUndoStringId()
|
||||
{
|
||||
return R.string.toast_habit_archived;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.isoron.uhabits.dialogs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
public class WeekdayPickerDialog extends DialogFragment
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.DialogFragment;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
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.Button;
|
||||
import android.widget.ImageButton;
|
||||
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.helpers.ColorHelper;
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.helpers.DialogHelper.OnSavedListener;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EditHabitFragment extends DialogFragment
|
||||
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
|
||||
TimePickerDialog.OnTimeSetListener
|
||||
{
|
||||
private Integer mode;
|
||||
static final int EDIT_MODE = 0;
|
||||
static final int CREATE_MODE = 1;
|
||||
|
||||
private OnSavedListener onSavedListener;
|
||||
|
||||
private Habit originalHabit, modifiedHabit;
|
||||
private TextView tvName, tvDescription, tvFreqNum, tvFreqDen, tvReminderTime, tvReminderDays;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private boolean is24HourMode;
|
||||
|
||||
static EditHabitFragment editSingleHabitFragment(long id)
|
||||
{
|
||||
EditHabitFragment frag = new EditHabitFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("habitId", id);
|
||||
args.putInt("editMode", EDIT_MODE);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
static EditHabitFragment createHabitFragment()
|
||||
{
|
||||
EditHabitFragment frag = new EditHabitFragment();
|
||||
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);
|
||||
|
||||
Button buttonSave = (Button) view.findViewById(R.id.buttonSave);
|
||||
Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard);
|
||||
|
||||
buttonSave.setOnClickListener(this);
|
||||
buttonDiscard.setOnClickListener(this);
|
||||
tvReminderTime.setOnClickListener(this);
|
||||
tvReminderDays.setOnClickListener(this);
|
||||
|
||||
ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor);
|
||||
|
||||
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();
|
||||
|
||||
int defaultNum = prefs.getInt("pref_default_habit_freq_num", modifiedHabit.freqNum);
|
||||
int defaultDen = prefs.getInt("pref_default_habit_freq_den", modifiedHabit.freqDen);
|
||||
int defaultColor = prefs.getInt("pref_default_habit_color", modifiedHabit.color);
|
||||
|
||||
modifiedHabit.color = defaultColor;
|
||||
modifiedHabit.freqNum = defaultNum;
|
||||
modifiedHabit.freqDen = defaultDen;
|
||||
}
|
||||
else if (mode == EDIT_MODE)
|
||||
{
|
||||
originalHabit = Habit.get((Long) args.get("habitId"));
|
||||
modifiedHabit = new Habit(originalHabit);
|
||||
|
||||
getDialog().setTitle(R.string.edit_habit);
|
||||
tvName.append(modifiedHabit.name);
|
||||
tvDescription.append(modifiedHabit.description);
|
||||
}
|
||||
|
||||
tvFreqNum.append(modifiedHabit.freqNum.toString());
|
||||
tvFreqDen.append(modifiedHabit.freqDen.toString());
|
||||
|
||||
changeColor(modifiedHabit.color);
|
||||
updateReminder();
|
||||
|
||||
buttonPickColor.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void changeColor(Integer color)
|
||||
{
|
||||
modifiedHabit.color = color;
|
||||
tvName.setTextColor(color);
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt("pref_default_habit_color", color);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void updateReminder()
|
||||
{
|
||||
if (modifiedHabit.reminderHour != null)
|
||||
{
|
||||
tvReminderTime.setTextColor(Color.BLACK);
|
||||
tvReminderTime.setText(DateHelper.formatTime(getActivity(), modifiedHabit.reminderHour,
|
||||
modifiedHabit.reminderMin));
|
||||
tvReminderDays.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
tvReminderTime.setTextColor(Color.GRAY);
|
||||
tvReminderTime.setText(R.string.reminder_off);
|
||||
tvReminderDays.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays);
|
||||
tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
ColorPickerDialog picker = ColorPickerDialog.newInstance(
|
||||
R.string.color_picker_default_title, ColorHelper.palette, modifiedHabit.color, 4,
|
||||
ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int color)
|
||||
{
|
||||
changeColor(color);
|
||||
}
|
||||
});
|
||||
picker.show(getFragmentManager(), "picker");
|
||||
}
|
||||
|
||||
private void onSaveButtonClick()
|
||||
{
|
||||
Command command = null;
|
||||
|
||||
modifiedHabit.name = tvName.getText().toString().trim();
|
||||
modifiedHabit.description = tvDescription.getText().toString().trim();
|
||||
modifiedHabit.freqNum = Integer.parseInt(tvFreqNum.getText().toString());
|
||||
modifiedHabit.freqDen = Integer.parseInt(tvFreqDen.getText().toString());
|
||||
|
||||
if (!validate()) return;
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt("pref_default_habit_freq_num", modifiedHabit.freqNum);
|
||||
editor.putInt("pref_default_habit_freq_den", modifiedHabit.freqDen);
|
||||
editor.apply();
|
||||
|
||||
Habit savedHabit = null;
|
||||
|
||||
if (mode == EDIT_MODE)
|
||||
{
|
||||
command = new EditHabitCommand(originalHabit, modifiedHabit);
|
||||
savedHabit = originalHabit;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void onDateSpinnerClick()
|
||||
{
|
||||
int defaultHour = 8;
|
||||
int defaultMin = 0;
|
||||
|
||||
if (modifiedHabit.reminderHour != null)
|
||||
{
|
||||
defaultHour = modifiedHabit.reminderHour;
|
||||
defaultMin = modifiedHabit.reminderMin;
|
||||
}
|
||||
|
||||
TimePickerDialog timePicker =
|
||||
TimePickerDialog.newInstance(this, defaultHour, defaultMin, is24HourMode);
|
||||
timePicker.show(getFragmentManager(), "timePicker");
|
||||
}
|
||||
|
||||
private void onWeekdayClick()
|
||||
{
|
||||
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;
|
||||
updateReminder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeCleared(RadialPickerLayout view)
|
||||
{
|
||||
modifiedHabit.reminderHour = null;
|
||||
modifiedHabit.reminderMin = null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,839 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.colorpicker.ColorPickerDialog;
|
||||
import com.android.colorpicker.ColorPickerSwatch;
|
||||
import com.mobeta.android.dslv.DragSortController;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
import com.mobeta.android.dslv.DragSortListView.DropListener;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.helpers.DialogHelper.OnSavedListener;
|
||||
import org.isoron.helpers.ReplayableActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
|
||||
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.io.CSVExporter;
|
||||
import org.isoron.uhabits.loaders.HabitListLoader;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListHabitsFragment extends Fragment
|
||||
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
||||
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener
|
||||
{
|
||||
private class ListHabitsActionBarCallback implements ActionMode.Callback
|
||||
{
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
|
||||
updateTitle(mode);
|
||||
updateActions(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
|
||||
{
|
||||
updateTitle(mode);
|
||||
updateActions(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateActions(Menu menu)
|
||||
{
|
||||
boolean showEdit = (selectedPositions.size() == 1);
|
||||
boolean showColor = true;
|
||||
boolean showArchive = true;
|
||||
boolean showUnarchive = true;
|
||||
|
||||
if(showEdit) showColor = false;
|
||||
for(int i : selectedPositions)
|
||||
{
|
||||
Habit h = loader.habitsList.get(i);
|
||||
if(h.isArchived())
|
||||
{
|
||||
showColor = false;
|
||||
showArchive = false;
|
||||
}
|
||||
else showUnarchive = false;
|
||||
}
|
||||
|
||||
MenuItem itemEdit = menu.findItem(R.id.action_edit_habit);
|
||||
MenuItem itemColor = menu.findItem(R.id.action_color);
|
||||
MenuItem itemArchive = menu.findItem(R.id.action_archive_habit);
|
||||
MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit);
|
||||
|
||||
itemEdit.setVisible(showEdit);
|
||||
itemColor.setVisible(showColor);
|
||||
itemArchive.setVisible(showArchive);
|
||||
itemUnarchive.setVisible(showUnarchive);
|
||||
}
|
||||
|
||||
private void updateTitle(ActionMode mode)
|
||||
{
|
||||
mode.setTitle("" + selectedPositions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, MenuItem item)
|
||||
{
|
||||
final LinkedList<Habit> selectedHabits = new LinkedList<>();
|
||||
for(int i : selectedPositions)
|
||||
selectedHabits.add(loader.habitsList.get(i));
|
||||
|
||||
Habit firstHabit = selectedHabits.getFirst();
|
||||
|
||||
switch(item.getItemId())
|
||||
{
|
||||
case R.id.action_archive_habit:
|
||||
executeCommand(new ArchiveHabitsCommand(selectedHabits), null);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_unarchive_habit:
|
||||
executeCommand(new UnarchiveHabitsCommand(selectedHabits), null);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_edit_habit:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId());
|
||||
frag.setOnSavedListener(ListHabitsFragment.this);
|
||||
frag.show(getFragmentManager(), "dialog");
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_color:
|
||||
{
|
||||
ColorPickerDialog picker = ColorPickerDialog.newInstance(
|
||||
R.string.color_picker_default_title, ColorHelper.palette,
|
||||
firstHabit.color, 4, ColorPickerDialog.SIZE_SMALL);
|
||||
|
||||
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
|
||||
{
|
||||
public void onColorSelected(int color)
|
||||
{
|
||||
executeCommand(new ChangeHabitColorCommand(selectedHabits, color), null);
|
||||
mode.finish();
|
||||
}
|
||||
});
|
||||
picker.show(getFragmentManager(), "picker");
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_delete:
|
||||
{
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_habits)
|
||||
.setMessage(R.string.delete_habits_message)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
executeCommand(new DeleteHabitsCommand(selectedHabits), null);
|
||||
mode.finish();
|
||||
}
|
||||
}).setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_export_csv:
|
||||
{
|
||||
onExportHabitsClick(selectedHabits);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode)
|
||||
{
|
||||
actionMode = null;
|
||||
|
||||
selectedPositions.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
listView.setDragEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static final int INACTIVE_COLOR = Color.rgb(200, 200, 200);
|
||||
public static final int INACTIVE_CHECKMARK_COLOR = Color.rgb(230, 230, 230);
|
||||
|
||||
public static final int HINT_INTERVAL = 5;
|
||||
public static final int HINT_INTERVAL_OFFSET = 2;
|
||||
|
||||
public interface OnHabitClickListener
|
||||
{
|
||||
void onHabitClicked(Habit habit);
|
||||
}
|
||||
|
||||
ListHabitsAdapter adapter;
|
||||
DragSortListView listView;
|
||||
ReplayableActivity activity;
|
||||
TextView tvNameHeader;
|
||||
long lastLongClick = 0;
|
||||
|
||||
private int tvNameWidth;
|
||||
private int buttonCount;
|
||||
private View llEmpty;
|
||||
private View llHint;
|
||||
|
||||
private OnHabitClickListener habitClickListener;
|
||||
private boolean isShortToggleEnabled;
|
||||
|
||||
private HabitListLoader loader;
|
||||
private boolean showArchived;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
private ActionMode actionMode;
|
||||
private List<Integer> selectedPositions;
|
||||
private DragSortController dragSortController;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
int width = (int) (dm.widthPixels / dm.density);
|
||||
buttonCount = Math.max(0, (int) ((width - 160) / 42.0));
|
||||
tvNameWidth = (int) ((width - 30 - buttonCount * 42) * dm.density);
|
||||
|
||||
loader = new HabitListLoader();
|
||||
loader.setListener(this);
|
||||
loader.setCheckmarkCount(buttonCount);
|
||||
|
||||
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
|
||||
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
|
||||
|
||||
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
loader.setProgressBar(progressBar);
|
||||
|
||||
adapter = new ListHabitsAdapter(getActivity());
|
||||
listView = (DragSortListView) view.findViewById(R.id.listView);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener(this);
|
||||
listView.setOnItemLongClickListener(this);
|
||||
listView.setDropListener(this);
|
||||
listView.setDragListener(new DragSortListView.DragListener()
|
||||
{
|
||||
@Override
|
||||
public void drag(int from, int to)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDrag(int position)
|
||||
{
|
||||
selectItem(position);
|
||||
}
|
||||
});
|
||||
|
||||
dragSortController = new DragSortController(listView) {
|
||||
@Override
|
||||
public View onCreateFloatView(int position)
|
||||
{
|
||||
return adapter.getView(position, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyFloatView(View floatView)
|
||||
{
|
||||
}
|
||||
};
|
||||
dragSortController.setRemoveEnabled(false);
|
||||
|
||||
listView.setFloatViewManager(dragSortController);
|
||||
listView.setDragEnabled(true);
|
||||
listView.setLongClickable(true);
|
||||
|
||||
llHint = view.findViewById(R.id.llHint);
|
||||
llHint.setOnClickListener(this);
|
||||
|
||||
Typeface fontawesome = Typeface.createFromAsset(getActivity().getAssets(),
|
||||
"fontawesome-webfont.ttf");
|
||||
((TextView) view.findViewById(R.id.tvStarEmpty)).setTypeface(fontawesome);
|
||||
llEmpty = view.findViewById(R.id.llEmpty);
|
||||
|
||||
loader.updateAllHabits(true);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
selectedPositions = new LinkedList<>();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onAttach(Activity activity)
|
||||
{
|
||||
super.onAttach(activity);
|
||||
this.activity = (ReplayableActivity) activity;
|
||||
|
||||
habitClickListener = (OnHabitClickListener) activity;
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
Long timestamp = loader.getLastLoadTimestamp();
|
||||
|
||||
if (timestamp != null && timestamp != DateHelper.getStartOfToday())
|
||||
loader.updateAllHabits(true);
|
||||
|
||||
updateEmptyMessage();
|
||||
updateHeader();
|
||||
showNextHint();
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
isShortToggleEnabled = prefs.getBoolean("pref_short_toggle", false);
|
||||
}
|
||||
|
||||
private void updateHeader()
|
||||
{
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = getView();
|
||||
|
||||
if (view == null) return;
|
||||
|
||||
GregorianCalendar day = DateHelper.getStartOfTodayCalendar();
|
||||
|
||||
LinearLayout llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader);
|
||||
llButtonsHeader.removeAllViews();
|
||||
|
||||
for (int i = 0; i < buttonCount; i++)
|
||||
{
|
||||
View tvDay = inflater.inflate(R.layout.list_habits_header_check, null);
|
||||
Button btCheck = (Button) tvDay.findViewById(R.id.tvCheck);
|
||||
btCheck.setText(DateHelper.formatHeaderDate(day));
|
||||
llButtonsHeader.addView(tvDay);
|
||||
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished()
|
||||
{
|
||||
adapter.notifyDataSetChanged();
|
||||
updateEmptyMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
|
||||
{
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.list_habits_options, menu);
|
||||
|
||||
MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived);
|
||||
showArchivedItem.setChecked(showArchived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
final Habit habit = loader.habits.get(info.id);
|
||||
|
||||
if (habit.isArchived()) menu.findItem(R.id.action_archive_habit).setVisible(false);
|
||||
else menu.findItem(R.id.action_unarchive_habit).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_add:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "dialog");
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_show_archived:
|
||||
{
|
||||
showArchived = !showArchived;
|
||||
loader.setIncludeArchived(showArchived);
|
||||
loader.updateAllHabits(true);
|
||||
activity.invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView parent, View view, int position, long id)
|
||||
{
|
||||
if (new Date().getTime() - lastLongClick < 1000) return;
|
||||
|
||||
if(actionMode == null)
|
||||
{
|
||||
Habit habit = loader.habitsList.get(position);
|
||||
habitClickListener.onHabitClicked(habit);
|
||||
}
|
||||
else
|
||||
{
|
||||
int k = selectedPositions.indexOf(position);
|
||||
if(k < 0)
|
||||
selectedPositions.add(position);
|
||||
else
|
||||
selectedPositions.remove(k);
|
||||
|
||||
if(selectedPositions.isEmpty()) actionMode.finish();
|
||||
else actionMode.invalidate();
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
selectItem(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectItem(int position)
|
||||
{
|
||||
if(!selectedPositions.contains(position))
|
||||
selectedPositions.add(position);
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if(actionMode == null)
|
||||
{
|
||||
actionMode = getActivity().startActionMode(new ListHabitsActionBarCallback());
|
||||
// listView.setDragEnabled(false);
|
||||
}
|
||||
|
||||
if(actionMode != null) actionMode.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaved(Command command, Object savedObject)
|
||||
{
|
||||
Habit h = (Habit) savedObject;
|
||||
|
||||
if (h == null) activity.executeCommand(command, null);
|
||||
else activity.executeCommand(command, h.getId());
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
ReminderHelper.createReminderAlarms(activity);
|
||||
|
||||
if(actionMode != null) actionMode.finish();
|
||||
}
|
||||
|
||||
private void updateEmptyMessage()
|
||||
{
|
||||
if (loader.getLastLoadTimestamp() == null) llEmpty.setVisibility(View.GONE);
|
||||
else llEmpty.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v)
|
||||
{
|
||||
lastLongClick = new Date().getTime();
|
||||
|
||||
switch (v.getId())
|
||||
{
|
||||
case R.id.tvCheck:
|
||||
onCheckmarkLongClick(v);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onCheckmarkLongClick(View v)
|
||||
{
|
||||
if (isShortToggleEnabled) return;
|
||||
|
||||
toggleCheck(v);
|
||||
DialogHelper.vibrate(activity, 100);
|
||||
}
|
||||
|
||||
private void toggleCheck(View v)
|
||||
{
|
||||
Long tag = (Long) v.getTag(R.string.habit_key);
|
||||
Habit habit = loader.habits.get(tag);
|
||||
|
||||
int offset = (Integer) v.getTag(R.string.offset_key);
|
||||
long timestamp = DateHelper.getStartOfDay(
|
||||
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
|
||||
|
||||
if (v.getTag(R.string.toggle_key).equals(2)) updateCheckmark(habit.color, (TextView) v, 0);
|
||||
else updateCheckmark(habit.color, (TextView) v, 2);
|
||||
|
||||
executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId());
|
||||
}
|
||||
|
||||
private void executeCommand(Command c, Long refreshKey)
|
||||
{
|
||||
activity.executeCommand(c, refreshKey);
|
||||
}
|
||||
|
||||
private void hideHint()
|
||||
{
|
||||
llHint.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation)
|
||||
{
|
||||
llHint.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showNextHint()
|
||||
{
|
||||
Integer lastHintNumber = prefs.getInt("last_hint_number", -1);
|
||||
Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1);
|
||||
|
||||
if(DateHelper.getStartOfToday() > lastHintTimestamp)
|
||||
showHint(lastHintNumber + 1);
|
||||
}
|
||||
|
||||
private void showHint(int hintNumber)
|
||||
{
|
||||
String[] hints = activity.getResources().getStringArray(R.array.hints);
|
||||
if(hintNumber >= hints.length) return;
|
||||
|
||||
prefs.edit().putInt("last_hint_number", hintNumber).apply();
|
||||
prefs.edit().putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply();
|
||||
|
||||
TextView tvContent = (TextView) llHint.findViewById(R.id.hintContent);
|
||||
tvContent.setText(hints[hintNumber]);
|
||||
|
||||
llHint.setAlpha(0.0f);
|
||||
llHint.setVisibility(View.VISIBLE);
|
||||
llHint.animate().alpha(1f).setDuration(500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drop(int from, int to)
|
||||
{
|
||||
if(from == to) return;
|
||||
if(actionMode != null) actionMode.finish();
|
||||
|
||||
loader.reorder(from, to);
|
||||
adapter.notifyDataSetChanged();
|
||||
loader.updateAllHabits(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
switch (v.getId())
|
||||
{
|
||||
case R.id.tvCheck:
|
||||
if (isShortToggleEnabled) toggleCheck(v);
|
||||
else activity.showToast(R.string.long_press_to_toggle);
|
||||
break;
|
||||
|
||||
case R.id.llHint:
|
||||
hideHint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class ListHabitsAdapter extends BaseAdapter
|
||||
{
|
||||
private LayoutInflater inflater;
|
||||
private Typeface fontawesome;
|
||||
|
||||
public ListHabitsAdapter(Context context)
|
||||
{
|
||||
|
||||
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount()
|
||||
{
|
||||
return loader.habits.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position)
|
||||
{
|
||||
return loader.habitsList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position)
|
||||
{
|
||||
return ((Habit) getItem(position)).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent)
|
||||
{
|
||||
final Habit habit = loader.habitsList.get(position);
|
||||
|
||||
if (view == null ||
|
||||
(Long) view.getTag(R.id.timestamp_key) != DateHelper.getStartOfToday())
|
||||
{
|
||||
view = inflater.inflate(R.layout.list_habits_item, null);
|
||||
((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome);
|
||||
|
||||
LinearLayout.LayoutParams params =
|
||||
new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1);
|
||||
view.findViewById(R.id.label).setLayoutParams(params);
|
||||
|
||||
inflateCheckmarkButtons(view);
|
||||
|
||||
view.setTag(R.id.timestamp_key, DateHelper.getStartOfToday());
|
||||
}
|
||||
|
||||
TextView tvStar = ((TextView) view.findViewById(R.id.tvStar));
|
||||
TextView tvName = (TextView) view.findViewById(R.id.label);
|
||||
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
|
||||
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
|
||||
|
||||
llInner.setTag(R.string.habit_key, habit.getId());
|
||||
|
||||
updateNameAndIcon(habit, tvStar, tvName);
|
||||
updateCheckmarkButtons(habit, llButtons);
|
||||
|
||||
boolean selected = selectedPositions.contains(position);
|
||||
if(selected)
|
||||
llInner.setBackgroundResource(R.drawable.selected_box);
|
||||
else
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
llInner.setBackgroundResource(R.drawable.ripple_white);
|
||||
else
|
||||
llInner.setBackgroundResource(R.drawable.card_background);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void inflateCheckmarkButtons(View view)
|
||||
{
|
||||
for (int i = 0; i < buttonCount; i++)
|
||||
{
|
||||
View check = inflater.inflate(R.layout.list_habits_item_check, null);
|
||||
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck);
|
||||
btCheck.setTypeface(fontawesome);
|
||||
btCheck.setOnLongClickListener(ListHabitsFragment.this);
|
||||
btCheck.setOnClickListener(ListHabitsFragment.this);
|
||||
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCheckmarkButtons(Habit habit, LinearLayout llButtons)
|
||||
{
|
||||
int activeColor = getActiveColor(habit);
|
||||
int m = llButtons.getChildCount();
|
||||
Long habitId = habit.getId();
|
||||
|
||||
int isChecked[] = loader.checkmarks.get(habitId);
|
||||
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
|
||||
TextView tvCheck = (TextView) llButtons.getChildAt(i);
|
||||
tvCheck.setTag(R.string.habit_key, habitId);
|
||||
tvCheck.setTag(R.string.offset_key, i);
|
||||
if(isChecked.length > i)
|
||||
updateCheckmark(activeColor, tvCheck, isChecked[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName)
|
||||
{
|
||||
int activeColor = getActiveColor(habit);
|
||||
|
||||
tvName.setText(habit.name);
|
||||
tvName.setTextColor(activeColor);
|
||||
|
||||
if (habit.isArchived())
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_archive));
|
||||
tvStar.setTextColor(activeColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
int score = loader.scores.get(habit.getId());
|
||||
|
||||
if (score < Score.HALF_STAR_CUTOFF)
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_star_o));
|
||||
tvStar.setTextColor(INACTIVE_COLOR);
|
||||
}
|
||||
else if (score < Score.FULL_STAR_CUTOFF)
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_star_half_o));
|
||||
tvStar.setTextColor(INACTIVE_COLOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
tvStar.setText(getString(R.string.fa_star));
|
||||
tvStar.setTextColor(activeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getActiveColor(Habit habit)
|
||||
{
|
||||
int activeColor = habit.color;
|
||||
if(habit.isArchived()) activeColor = INACTIVE_COLOR;
|
||||
|
||||
return activeColor;
|
||||
}
|
||||
|
||||
private void updateCheckmark(int activeColor, TextView tvCheck, int check)
|
||||
{
|
||||
switch (check)
|
||||
{
|
||||
case 2:
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(activeColor);
|
||||
tvCheck.setTag(R.string.toggle_key, 2);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
tvCheck.setText(R.string.fa_check);
|
||||
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
|
||||
tvCheck.setTag(R.string.toggle_key, 1);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
tvCheck.setText(R.string.fa_times);
|
||||
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
|
||||
tvCheck.setTag(R.string.toggle_key, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void onPostExecuteCommand(Long refreshKey)
|
||||
{
|
||||
if (refreshKey == null) loader.updateAllHabits(true);
|
||||
else loader.updateHabit(refreshKey);
|
||||
}
|
||||
|
||||
private void onExportHabitsClick(final LinkedList<Habit> selectedHabits)
|
||||
{
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
String filename;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if(filename != null)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/zip");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename)));
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
CSVExporter exporter = new CSVExporter(activity, selectedHabits);
|
||||
filename = exporter.writeArchive();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences().
|
||||
registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
getPreferenceManager().getSharedPreferences().
|
||||
unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
|
||||
{
|
||||
BackupManager.dataChanged("org.isoron.uhabits");
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.Command;
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.ShowHabitActivity;
|
||||
import org.isoron.uhabits.helpers.ReminderHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
import org.isoron.uhabits.views.HabitScoreView;
|
||||
import org.isoron.uhabits.views.HabitStreakView;
|
||||
import org.isoron.uhabits.views.RingView;
|
||||
|
||||
public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedListener
|
||||
{
|
||||
protected ShowHabitActivity activity;
|
||||
private Habit habit;
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View view = inflater.inflate(R.layout.show_habit, container, false);
|
||||
activity = (ShowHabitActivity) getActivity();
|
||||
habit = activity.habit;
|
||||
|
||||
habit.checkmarks.rebuild();
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||
{
|
||||
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
|
||||
activity.getWindow().setStatusBarColor(darkerHabitColor);
|
||||
}
|
||||
|
||||
TextView tvHistory = (TextView) view.findViewById(R.id.tvHistory);
|
||||
TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview);
|
||||
TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength);
|
||||
TextView tvStreaks = (TextView) view.findViewById(R.id.tvStreaks);
|
||||
RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing);
|
||||
HabitStreakView streakView = (HabitStreakView) view.findViewById(R.id.streakView);
|
||||
HabitScoreView scoreView = (HabitScoreView) view.findViewById(R.id.scoreView);
|
||||
HabitHistoryView historyView = (HabitHistoryView) view.findViewById(R.id.historyView);
|
||||
|
||||
tvHistory.setTextColor(habit.color);
|
||||
tvOverview.setTextColor(habit.color);
|
||||
tvStrength.setTextColor(habit.color);
|
||||
tvStreaks.setTextColor(habit.color);
|
||||
|
||||
scoreRing.setColor(habit.color);
|
||||
scoreRing.setPercentage((float) habit.scores.getNewestValue() / Score.MAX_SCORE);
|
||||
streakView.setHabit(habit);
|
||||
scoreView.setHabit(habit);
|
||||
historyView.setHabit(habit);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
|
||||
{
|
||||
inflater.inflate(R.menu.show_habit_fragment_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.action_edit_habit:
|
||||
{
|
||||
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
|
||||
frag.setOnSavedListener(this);
|
||||
frag.show(getFragmentManager(), "dialog");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaved(Command command, Object savedObject)
|
||||
{
|
||||
Habit h = (Habit) savedObject;
|
||||
|
||||
if (h == null) activity.executeCommand(command, null);
|
||||
else activity.executeCommand(command, h.getId());
|
||||
|
||||
ReminderHelper.createReminderAlarms(activity);
|
||||
activity.recreate();
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.helpers;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
public class ReminderHelper
|
||||
{
|
||||
public static void createReminderAlarms(Context context)
|
||||
{
|
||||
for (Habit habit : Habit.getHabitsWithReminder())
|
||||
createReminderAlarm(context, habit, null);
|
||||
}
|
||||
|
||||
public static void createReminderAlarm(Context context, Habit habit, Long reminderTime)
|
||||
{
|
||||
if (reminderTime == null)
|
||||
{
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(System.currentTimeMillis());
|
||||
calendar.set(Calendar.HOUR_OF_DAY, habit.reminderHour);
|
||||
calendar.set(Calendar.MINUTE, habit.reminderMin);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
|
||||
reminderTime = calendar.getTimeInMillis();
|
||||
|
||||
if (System.currentTimeMillis() > reminderTime)
|
||||
reminderTime += AlarmManager.INTERVAL_DAY;
|
||||
}
|
||||
|
||||
long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime));
|
||||
|
||||
Uri uri = habit.getUri();
|
||||
|
||||
Intent alarmIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||
alarmIntent.setAction(HabitBroadcastReceiver.ACTION_SHOW_REMINDER);
|
||||
alarmIntent.setData(uri);
|
||||
alarmIntent.putExtra("timestamp", timestamp);
|
||||
alarmIntent.putExtra("reminderTime", reminderTime);
|
||||
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getBroadcast(context, ((int) (habit.getId() % Integer.MAX_VALUE)) + 1,
|
||||
alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= 19)
|
||||
manager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
|
||||
else
|
||||
manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
|
||||
|
||||
Log.d("ReminderHelper", String.format("Setting alarm (%s): %s",
|
||||
DateFormat.getDateTimeInstance().format(new Date(reminderTime)), habit.name));
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package org.isoron.uhabits.io;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class CSVExporter
|
||||
{
|
||||
private List<Habit> habits;
|
||||
private Context context;
|
||||
private java.text.DateFormat dateFormat;
|
||||
|
||||
private List<String> generateDirs;
|
||||
private List<String> generateFilenames;
|
||||
|
||||
private String basePath;
|
||||
|
||||
public CSVExporter(Context context, List<Habit> habits)
|
||||
{
|
||||
this.habits = habits;
|
||||
this.context = context;
|
||||
generateDirs = new LinkedList<>();
|
||||
generateFilenames = new LinkedList<>();
|
||||
|
||||
basePath = String.format("%s/export/", context.getFilesDir());
|
||||
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public String formatDate(long timestamp)
|
||||
{
|
||||
return dateFormat.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
public String formatScore(int score)
|
||||
{
|
||||
return String.format("%.2f", ((float) score) / Score.MAX_SCORE);
|
||||
}
|
||||
|
||||
private void writeScores(String dirPath, Habit habit) throws IOException
|
||||
{
|
||||
String path = dirPath + "scores.csv";
|
||||
FileWriter out = new FileWriter(basePath + path);
|
||||
generateFilenames.add(path);
|
||||
|
||||
String query = "select timestamp, score from score where habit = ? order by timestamp";
|
||||
String params[] = { habit.getId().toString() };
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return;
|
||||
|
||||
do
|
||||
{
|
||||
String timestamp = formatDate(cursor.getLong(0));
|
||||
String score = formatScore(cursor.getInt(1));
|
||||
out.write(String.format("%s,%s\n", timestamp, score));
|
||||
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
out.close();
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void writeCheckmarks(String dirPath, Habit habit) throws IOException
|
||||
{
|
||||
String path = dirPath + "checkmarks.csv";
|
||||
FileWriter out = new FileWriter(basePath + path);
|
||||
generateFilenames.add(path);
|
||||
|
||||
String query = "select timestamp, value from checkmarks where habit = ? order by timestamp";
|
||||
String params[] = { habit.getId().toString() };
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return;
|
||||
|
||||
do
|
||||
{
|
||||
String timestamp = formatDate(cursor.getLong(0));
|
||||
Integer value = cursor.getInt(1);
|
||||
out.write(String.format("%s,%d\n", timestamp, value));
|
||||
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
out.close();
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void writeFiles(Habit habit) throws IOException
|
||||
{
|
||||
String path = String.format("%s/", habit.name);
|
||||
new File(basePath + path).mkdirs();
|
||||
generateDirs.add(path);
|
||||
|
||||
writeScores(path, habit);
|
||||
writeCheckmarks(path, habit);
|
||||
}
|
||||
|
||||
private void writeZipFile(String zipFilename) throws IOException
|
||||
{
|
||||
FileOutputStream fos = new FileOutputStream(zipFilename);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
|
||||
for(String filename : generateFilenames)
|
||||
addFileToZip(zos, filename);
|
||||
|
||||
zos.close();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void addFileToZip(ZipOutputStream zos, String filename) throws IOException
|
||||
{
|
||||
FileInputStream fis = new FileInputStream(new File(basePath + filename));
|
||||
ZipEntry ze = new ZipEntry(filename);
|
||||
zos.putNextEntry(ze);
|
||||
|
||||
int length;
|
||||
byte bytes[] = new byte[1024];
|
||||
while((length = fis.read(bytes)) >= 0)
|
||||
zos.write(bytes, 0, length);
|
||||
|
||||
zos.closeEntry();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
private void cleanup()
|
||||
{
|
||||
for(String filename : generateFilenames)
|
||||
new File(basePath + filename).delete();
|
||||
|
||||
for(String filename : generateDirs)
|
||||
new File(basePath + filename).delete();
|
||||
|
||||
new File(basePath).delete();
|
||||
}
|
||||
|
||||
public String writeArchive()
|
||||
{
|
||||
String date = formatDate(DateHelper.getStartOfToday());
|
||||
|
||||
File dir = context.getExternalCacheDir();
|
||||
|
||||
if(dir == null)
|
||||
{
|
||||
Log.e("CSVExporter", "No suitable directory found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String zipFilename = String.format("%s/habits-%s.zip", dir, date);
|
||||
|
||||
try
|
||||
{
|
||||
for (Habit h : habits)
|
||||
writeFiles(h);
|
||||
|
||||
writeZipFile(zipFilename);
|
||||
cleanup();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return zipFilename;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.loaders;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitListLoader
|
||||
{
|
||||
public interface Listener
|
||||
{
|
||||
void onLoadFinished();
|
||||
}
|
||||
|
||||
private AsyncTask<Void, Integer, Void> currentFetchTask;
|
||||
private int checkmarkCount;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private Listener listener;
|
||||
private Long lastLoadTimestamp;
|
||||
|
||||
public HashMap<Long, Habit> habits;
|
||||
public List<Habit> habitsList;
|
||||
public HashMap<Long, int[]> checkmarks;
|
||||
public HashMap<Long, Integer> scores;
|
||||
|
||||
boolean includeArchived;
|
||||
|
||||
public void setIncludeArchived(boolean includeArchived)
|
||||
{
|
||||
this.includeArchived = includeArchived;
|
||||
}
|
||||
|
||||
public void setProgressBar(ProgressBar progressBar)
|
||||
{
|
||||
this.progressBar = progressBar;
|
||||
}
|
||||
|
||||
public void setCheckmarkCount(int checkmarkCount)
|
||||
{
|
||||
this.checkmarkCount = checkmarkCount;
|
||||
}
|
||||
|
||||
public void setListener(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public Long getLastLoadTimestamp()
|
||||
{
|
||||
return lastLoadTimestamp;
|
||||
}
|
||||
|
||||
public HabitListLoader()
|
||||
{
|
||||
habits = new HashMap<>();
|
||||
checkmarks = new HashMap<>();
|
||||
scores = new HashMap<>();
|
||||
}
|
||||
|
||||
public void reorder(int from, int to)
|
||||
{
|
||||
Habit fromHabit = habitsList.get(from);
|
||||
Habit toHabit = habitsList.get(to);
|
||||
|
||||
habitsList.remove(from);
|
||||
habitsList.add(to, fromHabit);
|
||||
|
||||
Habit.reorder(fromHabit, toHabit);
|
||||
}
|
||||
|
||||
public void updateAllHabits(final boolean updateScoresAndCheckmarks)
|
||||
{
|
||||
if (currentFetchTask != null) currentFetchTask.cancel(true);
|
||||
|
||||
currentFetchTask = new AsyncTask<Void, Integer, Void>()
|
||||
{
|
||||
public HashMap<Long, Habit> newHabits;
|
||||
public HashMap<Long, int[]> newCheckmarks;
|
||||
public HashMap<Long, Integer> newScores;
|
||||
public List<Habit> newHabitList;
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
newHabits = new HashMap<>();
|
||||
newCheckmarks = new HashMap<>();
|
||||
newScores = new HashMap<>();
|
||||
newHabitList = Habit.getAll(includeArchived);
|
||||
|
||||
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
||||
long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay;
|
||||
int[] empty = new int[checkmarkCount];
|
||||
|
||||
for(Habit h : newHabitList)
|
||||
{
|
||||
Long id = h.getId();
|
||||
|
||||
newHabits.put(id, h);
|
||||
|
||||
if(checkmarks.containsKey(id))
|
||||
newCheckmarks.put(id, checkmarks.get(id));
|
||||
else
|
||||
newCheckmarks.put(id, empty);
|
||||
|
||||
if(scores.containsKey(id))
|
||||
newScores.put(id, scores.get(id));
|
||||
else
|
||||
newScores.put(id, 0);
|
||||
}
|
||||
|
||||
commit();
|
||||
|
||||
if(!updateScoresAndCheckmarks) return null;
|
||||
|
||||
int current = 0;
|
||||
for (Habit h : newHabitList)
|
||||
{
|
||||
if (isCancelled()) return null;
|
||||
|
||||
Long id = h.getId();
|
||||
newScores.put(id, h.scores.getNewestValue());
|
||||
newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
publishProgress(current++, newHabits.size());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void commit()
|
||||
{
|
||||
habits = newHabits;
|
||||
scores = newScores;
|
||||
checkmarks = newCheckmarks;
|
||||
habitsList = newHabitList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
if(progressBar != null)
|
||||
{
|
||||
progressBar.setIndeterminate(false);
|
||||
progressBar.setProgress(0);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values)
|
||||
{
|
||||
if(progressBar != null)
|
||||
{
|
||||
progressBar.setMax(values[1]);
|
||||
progressBar.setProgress(values[0]);
|
||||
}
|
||||
|
||||
if(listener != null) listener.onLoadFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if (isCancelled()) return;
|
||||
|
||||
if(progressBar != null) progressBar.setVisibility(View.INVISIBLE);
|
||||
lastLoadTimestamp = DateHelper.getStartOfToday();
|
||||
currentFetchTask = null;
|
||||
|
||||
if(listener != null) listener.onLoadFinished();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
currentFetchTask.execute();
|
||||
}
|
||||
|
||||
public void updateHabit(final Long id)
|
||||
{
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
{
|
||||
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
||||
long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay;
|
||||
|
||||
Habit h = Habit.get(id);
|
||||
habits.put(id, h);
|
||||
scores.put(id, h.scores.getNewestValue());
|
||||
checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
new Handler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (getStatus() == Status.RUNNING)
|
||||
{
|
||||
if(progressBar != null)
|
||||
{
|
||||
progressBar.setIndeterminate(true);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if(progressBar != null) progressBar.setVisibility(View.GONE);
|
||||
|
||||
if(listener != null)
|
||||
listener.onLoadFinished();
|
||||
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
|
||||
@Table(name = "Checkmarks")
|
||||
public class Checkmark extends Model
|
||||
{
|
||||
|
||||
public static final int UNCHECKED = 0;
|
||||
public static final int CHECKED_IMPLICITLY = 1;
|
||||
public static final int CHECKED_EXPLICITLY = 2;
|
||||
|
||||
@Column(name = "habit")
|
||||
public Habit habit;
|
||||
|
||||
@Column(name = "timestamp")
|
||||
public Long timestamp;
|
||||
|
||||
/**
|
||||
* Indicates whether there is a checkmark at the given timestamp or not, and whether the
|
||||
* checkmark is explicit or implicit. An explicit checkmark indicates that there is a
|
||||
* repetition at that day. An implicit checkmark indicates that there is no repetition at that
|
||||
* day, but a repetition was not needed, due to the frequency of the habit.
|
||||
*/
|
||||
@Column(name = "value")
|
||||
public Integer value;
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CheckmarkList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public CheckmarkList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Checkmark.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public int[] getValues(Long fromTimestamp, Long toTimestamp)
|
||||
{
|
||||
rebuild();
|
||||
|
||||
if(fromTimestamp > toTimestamp) return new int[0];
|
||||
|
||||
String query = "select value, timestamp from Checkmarks where " +
|
||||
"habit = ? and timestamp >= ? and timestamp <= ?";
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
String args[] = { habit.getId().toString(), fromTimestamp.toString(),
|
||||
toTimestamp.toString() };
|
||||
Cursor cursor = db.rawQuery(query, args);
|
||||
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
|
||||
int[] checks = new int[nDays];
|
||||
|
||||
if (cursor.moveToFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
long timestamp = cursor.getLong(1);
|
||||
int offset = (int) ((timestamp - fromTimestamp) / day);
|
||||
checks[nDays - offset - 1] = cursor.getInt(0);
|
||||
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
return checks;
|
||||
}
|
||||
|
||||
public int[] getAllValues()
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if(oldestRep == null) return new int[0];
|
||||
|
||||
Long toTimestamp = DateHelper.getStartOfToday();
|
||||
Long fromTimestamp = oldestRep.timestamp;
|
||||
return getValues(fromTimestamp, toTimestamp);
|
||||
}
|
||||
|
||||
public void rebuild()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Checkmark newestCheckmark = getNewest();
|
||||
if (newestCheckmark == null)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginning = newestCheckmark.timestamp + day;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
long beginningExtended = beginning - (long) (habit.freqDen) * day;
|
||||
List<Repetition> reps = habit.repetitions.selectFromTo(beginningExtended, today).execute();
|
||||
|
||||
int nDays = (int) ((today - beginning) / day) + 1;
|
||||
int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
|
||||
|
||||
int checks[] = new int[nDaysExtended];
|
||||
|
||||
// explicit checks
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
int offset = (int) ((rep.timestamp - beginningExtended) / day);
|
||||
checks[nDaysExtended - offset - 1] = 2;
|
||||
}
|
||||
|
||||
// implicit checks
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
for (int j = 0; j < habit.freqDen; j++)
|
||||
if (checks[i + j] == 2) counter++;
|
||||
|
||||
if (counter >= habit.freqNum) checks[i] = Math.max(checks[i], 1);
|
||||
}
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < nDays; i++)
|
||||
{
|
||||
Checkmark c = new Checkmark();
|
||||
c.habit = habit;
|
||||
c.timestamp = today - i * day;
|
||||
c.value = checks[i];
|
||||
c.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Checkmark getNewest()
|
||||
{
|
||||
return new Select().from(Checkmark.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public int getCurrentValue()
|
||||
{
|
||||
rebuild();
|
||||
Checkmark c = getNewest();
|
||||
|
||||
if(c != null) return c.value;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.From;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.activeandroid.query.Update;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "Habits")
|
||||
public class Habit extends Model
|
||||
{
|
||||
@Column(name = "name")
|
||||
public String name;
|
||||
|
||||
@Column(name = "description")
|
||||
public String description;
|
||||
|
||||
@Column(name = "freq_num")
|
||||
public Integer freqNum;
|
||||
|
||||
@Column(name = "freq_den")
|
||||
public Integer freqDen;
|
||||
|
||||
@Column(name = "color")
|
||||
public Integer color;
|
||||
|
||||
@Column(name = "position")
|
||||
public Integer position;
|
||||
|
||||
@Column(name = "reminder_hour")
|
||||
public Integer reminderHour;
|
||||
|
||||
@Column(name = "reminder_min")
|
||||
public Integer reminderMin;
|
||||
|
||||
@Column(name = "reminder_days")
|
||||
public Integer reminderDays;
|
||||
|
||||
@Column(name = "highlight")
|
||||
public Integer highlight;
|
||||
|
||||
@Column(name = "archived")
|
||||
public Integer archived;
|
||||
|
||||
public StreakList streaks;
|
||||
public ScoreList scores;
|
||||
public RepetitionList repetitions;
|
||||
public CheckmarkList checkmarks;
|
||||
|
||||
public Habit(Habit model)
|
||||
{
|
||||
copyAttributes(model);
|
||||
initializeLists();
|
||||
}
|
||||
|
||||
public Habit()
|
||||
{
|
||||
this.color = ColorHelper.palette[5];
|
||||
this.position = Habit.countWithArchived();
|
||||
this.highlight = 0;
|
||||
this.archived = 0;
|
||||
this.freqDen = 7;
|
||||
this.freqNum = 3;
|
||||
this.reminderDays = 127;
|
||||
initializeLists();
|
||||
}
|
||||
|
||||
private void initializeLists()
|
||||
{
|
||||
streaks = new StreakList(this);
|
||||
scores = new ScoreList(this);
|
||||
repetitions = new RepetitionList(this);
|
||||
checkmarks = new CheckmarkList(this);
|
||||
}
|
||||
|
||||
public static Habit get(Long id)
|
||||
{
|
||||
return Habit.load(Habit.class, id);
|
||||
}
|
||||
|
||||
public static List<Habit> getAll(boolean includeArchive)
|
||||
{
|
||||
if(includeArchive) return selectWithArchived().execute();
|
||||
else return select().execute();
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static void updateId(long oldId, long newId)
|
||||
{
|
||||
SQLiteUtils.execSql(String.format("update Habits set Id = %d where Id = %d", newId, oldId));
|
||||
}
|
||||
|
||||
protected static From select()
|
||||
{
|
||||
return new Select().from(Habit.class).where("archived = 0").orderBy("position");
|
||||
}
|
||||
|
||||
public static From selectWithArchived()
|
||||
{
|
||||
return new Select().from(Habit.class).orderBy("position");
|
||||
}
|
||||
|
||||
public static int count()
|
||||
{
|
||||
return select().count();
|
||||
}
|
||||
|
||||
public static int countWithArchived()
|
||||
{
|
||||
return selectWithArchived().count();
|
||||
}
|
||||
|
||||
public static java.util.List<Habit> getHighlightedHabits()
|
||||
{
|
||||
return select().where("highlight = 1")
|
||||
.orderBy("reminder_hour desc, reminder_min desc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static java.util.List<Habit> getHabitsWithReminder()
|
||||
{
|
||||
return select().where("reminder_hour is not null").execute();
|
||||
}
|
||||
|
||||
public static void reorder(Habit from, Habit to)
|
||||
{
|
||||
if(from == to) return;
|
||||
|
||||
if (to.position < from.position)
|
||||
{
|
||||
new Update(Habit.class).set("position = position + 1")
|
||||
.where("position >= ? and position < ?", to.position, from.position)
|
||||
.execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
new Update(Habit.class).set("position = position - 1")
|
||||
.where("position > ? and position <= ?", from.position, to.position)
|
||||
.execute();
|
||||
}
|
||||
|
||||
from.position = to.position;
|
||||
from.save();
|
||||
}
|
||||
|
||||
public static void rebuildOrder()
|
||||
{
|
||||
List<Habit> habits = selectWithArchived().execute();
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
try
|
||||
{
|
||||
int i = 0;
|
||||
for (Habit h : habits)
|
||||
{
|
||||
h.position = i++;
|
||||
h.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void copyAttributes(Habit model)
|
||||
{
|
||||
this.name = model.name;
|
||||
this.description = model.description;
|
||||
this.freqNum = model.freqNum;
|
||||
this.freqDen = model.freqDen;
|
||||
this.color = model.color;
|
||||
this.position = model.position;
|
||||
this.reminderHour = model.reminderHour;
|
||||
this.reminderMin = model.reminderMin;
|
||||
this.reminderDays = model.reminderDays;
|
||||
this.highlight = model.highlight;
|
||||
this.archived = model.archived;
|
||||
}
|
||||
|
||||
public void save(Long id)
|
||||
{
|
||||
save();
|
||||
Habit.updateId(getId(), id);
|
||||
}
|
||||
|
||||
public void cascadeDelete()
|
||||
{
|
||||
Long id = getId();
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
try
|
||||
{
|
||||
new Delete().from(Checkmark.class).where("habit = ?", id).execute();
|
||||
new Delete().from(Repetition.class).where("habit = ?", id).execute();
|
||||
new Delete().from(Score.class).where("habit = ?", id).execute();
|
||||
new Delete().from(Streak.class).where("habit = ?", id).execute();
|
||||
delete();
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public Uri getUri()
|
||||
{
|
||||
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
|
||||
}
|
||||
|
||||
public void archive()
|
||||
{
|
||||
archived = 1;
|
||||
save();
|
||||
}
|
||||
|
||||
public void unarchive()
|
||||
{
|
||||
archived = 0;
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean isArchived()
|
||||
{
|
||||
return archived != 0;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
|
||||
@Table(name = "Repetitions")
|
||||
public class Repetition extends Model
|
||||
{
|
||||
|
||||
@Column(name = "habit")
|
||||
public Habit habit;
|
||||
|
||||
@Column(name = "timestamp")
|
||||
public Long timestamp;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.From;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
public class RepetitionList
|
||||
{
|
||||
|
||||
private Habit habit;
|
||||
|
||||
public RepetitionList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
protected From select()
|
||||
{
|
||||
return new Select().from(Repetition.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp");
|
||||
}
|
||||
|
||||
protected From selectFromTo(long timeFrom, long timeTo)
|
||||
{
|
||||
return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
|
||||
}
|
||||
|
||||
public boolean contains(long timestamp)
|
||||
{
|
||||
int count = select().where("timestamp = ?", timestamp).count();
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
public void delete(long timestamp)
|
||||
{
|
||||
new Delete().from(Repetition.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp = ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Repetition getOldestNewerThan(long timestamp)
|
||||
{
|
||||
return select().where("timestamp > ?", timestamp).limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public void toggle(long timestamp)
|
||||
{
|
||||
if (contains(timestamp))
|
||||
{
|
||||
delete(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition rep = new Repetition();
|
||||
rep.habit = habit;
|
||||
rep.timestamp = timestamp;
|
||||
rep.save();
|
||||
}
|
||||
|
||||
habit.scores.deleteNewerThan(timestamp);
|
||||
habit.checkmarks.deleteNewerThan(timestamp);
|
||||
habit.streaks.deleteNewerThan(timestamp);
|
||||
}
|
||||
|
||||
public Repetition getOldest()
|
||||
{
|
||||
return (Repetition) select().limit(1).executeSingle();
|
||||
}
|
||||
|
||||
public boolean hasImplicitRepToday()
|
||||
{
|
||||
long today = DateHelper.getStartOfToday();
|
||||
int reps[] = habit.checkmarks.getValues(today - DateHelper.millisecondsInOneDay, today);
|
||||
return (reps[0] > 0);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
|
||||
@Table(name = "Score")
|
||||
public class Score extends Model
|
||||
{
|
||||
public static final int HALF_STAR_CUTOFF = 9629750;
|
||||
public static final int FULL_STAR_CUTOFF = 15407600;
|
||||
public static final int MAX_SCORE = 19259500;
|
||||
|
||||
@Column(name = "habit")
|
||||
public Habit habit;
|
||||
|
||||
@Column(name = "timestamp")
|
||||
public Long timestamp;
|
||||
|
||||
@Column(name = "score")
|
||||
public Integer score;
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
public class ScoreList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public ScoreList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public int getCurrentStarStatus()
|
||||
{
|
||||
int score = getNewestValue();
|
||||
|
||||
if(score >= Score.FULL_STAR_CUTOFF) return 2;
|
||||
else if(score >= Score.HALF_STAR_CUTOFF) return 1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public Score getNewest()
|
||||
{
|
||||
return new Select().from(Score.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("timestamp desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Score.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("timestamp >= ?", timestamp)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Integer getNewestValue()
|
||||
{
|
||||
int beginningScore;
|
||||
long beginningTime;
|
||||
|
||||
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
double freq = ((double) habit.freqNum) / habit.freqDen;
|
||||
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
|
||||
|
||||
Score newestScore = getNewest();
|
||||
if (newestScore == null)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return 0;
|
||||
beginningTime = oldestRep.timestamp;
|
||||
beginningScore = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
beginningTime = newestScore.timestamp + day;
|
||||
beginningScore = newestScore.score;
|
||||
}
|
||||
|
||||
long nDays = (today - beginningTime) / day;
|
||||
if (nDays < 0) return newestScore.score;
|
||||
|
||||
int reps[] = habit.checkmarks.getValues(beginningTime, today);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
int lastScore = beginningScore;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < reps.length; i++)
|
||||
{
|
||||
Score s = new Score();
|
||||
s.habit = habit;
|
||||
s.timestamp = beginningTime + day * i;
|
||||
s.score = (int) (lastScore * multiplier);
|
||||
if (reps[reps.length - i - 1] == 2)
|
||||
{
|
||||
s.score += 1000000;
|
||||
s.score = Math.min(s.score, Score.MAX_SCORE);
|
||||
}
|
||||
s.save();
|
||||
|
||||
lastScore = s.score;
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
} finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
|
||||
return lastScore;
|
||||
}
|
||||
|
||||
public int[] getAllValues(Long fromTimestamp, Long toTimestamp, Integer divisor)
|
||||
{
|
||||
Long offset = toTimestamp - (divisor - 1) * DateHelper.millisecondsInOneDay;
|
||||
|
||||
String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " +
|
||||
"where habit = ? and timestamp > ? and timestamp <= ? " +
|
||||
"group by time order by time desc";
|
||||
|
||||
String params[] = { offset.toString(), divisor.toString(), habit.getId().toString(),
|
||||
fromTimestamp.toString(), toTimestamp.toString()};
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst()) return new int[0];
|
||||
|
||||
int k = 0;
|
||||
int[] scores = new int[cursor.getCount()];
|
||||
|
||||
do
|
||||
{
|
||||
scores[k++] = (int) cursor.getLong(1);
|
||||
}
|
||||
while (cursor.moveToNext());
|
||||
|
||||
cursor.close();
|
||||
return scores;
|
||||
|
||||
}
|
||||
|
||||
public int[] getAllValues(int divisor)
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if(oldestRep == null) return new int[0];
|
||||
|
||||
long fromTimestamp = oldestRep.timestamp;
|
||||
long toTimestamp = DateHelper.getStartOfToday();
|
||||
return getAllValues(fromTimestamp, toTimestamp, divisor);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
|
||||
public class Streak extends Model
|
||||
{
|
||||
@Column(name = "habit")
|
||||
public Habit habit;
|
||||
|
||||
@Column(name = "start")
|
||||
public Long start;
|
||||
|
||||
@Column(name = "end")
|
||||
public Long end;
|
||||
|
||||
@Column(name = "length")
|
||||
public Long length;
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.helpers.DateHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StreakList
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
public StreakList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public List<Streak> getAll()
|
||||
{
|
||||
rebuild();
|
||||
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("end asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public Streak getNewest()
|
||||
{
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("end desc")
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public void rebuild()
|
||||
{
|
||||
long beginning;
|
||||
long today = DateHelper.getStartOfToday();
|
||||
long day = DateHelper.millisecondsInOneDay;
|
||||
|
||||
Streak newestStreak = getNewest();
|
||||
if (newestStreak != null)
|
||||
{
|
||||
beginning = newestStreak.start;
|
||||
}
|
||||
else
|
||||
{
|
||||
Repetition oldestRep = habit.repetitions.getOldest();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
beginning = oldestRep.timestamp;
|
||||
}
|
||||
|
||||
if (beginning > today) return;
|
||||
|
||||
int checks[] = habit.checkmarks.getValues(beginning, today);
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
|
||||
long current = beginning;
|
||||
list.add(current);
|
||||
|
||||
for (int i = 1; i < checks.length; i++)
|
||||
{
|
||||
current += day;
|
||||
int j = checks.length - i - 1;
|
||||
|
||||
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
|
||||
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
|
||||
}
|
||||
|
||||
if (list.size() % 2 == 1) list.add(current);
|
||||
|
||||
ActiveAndroid.beginTransaction();
|
||||
|
||||
if(newestStreak != null) newestStreak.delete();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < list.size(); i += 2)
|
||||
{
|
||||
Streak streak = new Streak();
|
||||
streak.habit = habit;
|
||||
streak.start = list.get(i);
|
||||
streak.end = list.get(i + 1);
|
||||
streak.length = (streak.end - streak.start) / day + 1;
|
||||
streak.save();
|
||||
}
|
||||
|
||||
ActiveAndroid.setTransactionSuccessful();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveAndroid.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void deleteNewerThan(long timestamp)
|
||||
{
|
||||
new Delete().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
public class CheckmarkView extends View
|
||||
{
|
||||
private Paint pCard;
|
||||
private Paint pIcon;
|
||||
|
||||
private int primaryColor;
|
||||
private int backgroundColor;
|
||||
private int timesColor;
|
||||
private int darkGrey;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private int leftMargin;
|
||||
private int topMargin;
|
||||
private int padding;
|
||||
private String label;
|
||||
|
||||
private String fa_check;
|
||||
private String fa_times;
|
||||
private String fa_full_star;
|
||||
private String fa_half_star;
|
||||
private String fa_empty_star;
|
||||
|
||||
private int check_status;
|
||||
private int star_status;
|
||||
|
||||
private Rect rect;
|
||||
private TextPaint textPaint;
|
||||
private StaticLayout labelLayout;
|
||||
|
||||
public CheckmarkView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public CheckmarkView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context)
|
||||
{
|
||||
Typeface fontawesome =
|
||||
Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
||||
|
||||
pCard = new Paint();
|
||||
pCard.setAntiAlias(true);
|
||||
|
||||
pIcon = new Paint();
|
||||
pIcon.setAntiAlias(true);
|
||||
pIcon.setTypeface(fontawesome);
|
||||
pIcon.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
textPaint = new TextPaint();
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setAntiAlias(true);
|
||||
|
||||
fa_check = context.getString(R.string.fa_check);
|
||||
fa_times = context.getString(R.string.fa_times);
|
||||
fa_empty_star = context.getString(R.string.fa_star_o);
|
||||
fa_half_star = context.getString(R.string.fa_star_half_o);
|
||||
fa_full_star = context.getString(R.string.fa_star);
|
||||
|
||||
primaryColor = ColorHelper.palette[10];
|
||||
backgroundColor = Color.argb(255, 255, 255, 255);
|
||||
timesColor = Color.argb(128, 255, 255, 255);
|
||||
darkGrey = Color.argb(64, 0, 0, 0);
|
||||
|
||||
rect = new Rect();
|
||||
check_status = 2;
|
||||
star_status = 0;
|
||||
label = "Wake up early";
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.check_status = habit.checkmarks.getCurrentValue();
|
||||
this.star_status = habit.scores.getCurrentStarStatus();
|
||||
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color), Color.blue(habit.color));
|
||||
this.label = habit.name;
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawBackground(canvas);
|
||||
drawCheckmark(canvas);
|
||||
drawLabel(canvas);
|
||||
}
|
||||
|
||||
private void drawBackground(Canvas canvas)
|
||||
{
|
||||
int color = (check_status == 2 ? primaryColor : darkGrey);
|
||||
|
||||
pCard.setColor(color);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
canvas.drawRoundRect(leftMargin, topMargin, width - leftMargin, height - topMargin, padding,
|
||||
padding, pCard);
|
||||
else
|
||||
canvas.drawRect(leftMargin, topMargin, width - leftMargin, height - topMargin, pCard);
|
||||
}
|
||||
|
||||
private void drawCheckmark(Canvas canvas)
|
||||
{
|
||||
String text = (check_status == 0 ? fa_times : fa_check);
|
||||
int color = (check_status == 2 ? Color.WHITE : timesColor);
|
||||
|
||||
pIcon.setColor(color);
|
||||
pIcon.setTextSize(width * 0.5f);
|
||||
pIcon.getTextBounds(text, 0, 1, rect);
|
||||
|
||||
// canvas.drawLine(0, 0.67f * height, width, 0.67f * height, pIcon);
|
||||
|
||||
int y = (int) ((0.67f * height - rect.bottom - rect.top) / 2);
|
||||
canvas.drawText(text, width / 2, y, pIcon);
|
||||
}
|
||||
|
||||
private void drawLabel(Canvas canvas)
|
||||
{
|
||||
canvas.save();
|
||||
float y;
|
||||
int nLines = labelLayout.getLineCount();
|
||||
|
||||
if(nLines == 1)
|
||||
y = height * 0.8f - padding;
|
||||
else
|
||||
y = height * 0.7f - padding;
|
||||
|
||||
canvas.translate(leftMargin + padding, y);
|
||||
|
||||
labelLayout.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
setMeasuredDimension(width, (int) (width * 1.25));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
this.width = getMeasuredWidth();
|
||||
this.height = getMeasuredHeight();
|
||||
|
||||
leftMargin = (int) (width * 0.015);
|
||||
topMargin = (int) (height * 0.015);
|
||||
padding = 8 * leftMargin;
|
||||
textPaint.setTextSize(0.15f * width);
|
||||
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
private void updateLabel()
|
||||
{
|
||||
textPaint.setColor(Color.WHITE);
|
||||
labelLayout = new StaticLayout(label, textPaint, width - 2 * leftMargin - 2 * padding,
|
||||
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Align;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitHistoryView extends ScrollableDataView
|
||||
{
|
||||
private Habit habit;
|
||||
private int[] checkmarks;
|
||||
private Paint pSquareBg, pSquareFg, pTextHeader;
|
||||
private int squareSpacing;
|
||||
|
||||
private float squareTextOffset;
|
||||
private float headerTextOffset;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int nColumns;
|
||||
private int baseSize;
|
||||
|
||||
private String wdays[];
|
||||
private SimpleDateFormat dfMonth;
|
||||
private SimpleDateFormat dfYear;
|
||||
|
||||
private Calendar baseDate;
|
||||
private int nDays;
|
||||
private int todayWeekday;
|
||||
private int colors[];
|
||||
private Rect baseLocation;
|
||||
private int primaryColor;
|
||||
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
|
||||
public HabitHistoryView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
init();
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
createColors();
|
||||
fetchData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
wdays = DateHelper.getShortDayNames();
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
|
||||
|
||||
baseLocation = new Rect();
|
||||
}
|
||||
|
||||
private void updateDate()
|
||||
{
|
||||
baseDate = new GregorianCalendar();
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
|
||||
|
||||
nDays = (nColumns - 1) * 7;
|
||||
todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
|
||||
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
|
||||
baseDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
if(height < 8) height = 200;
|
||||
|
||||
baseSize = height / 8;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
nColumns = width / baseSize;
|
||||
|
||||
squareSpacing = (int) Math.floor(baseSize / 15.0);
|
||||
pSquareFg.setTextSize(baseSize * 0.5f);
|
||||
pTextHeader.setTextSize(baseSize * 0.5f);
|
||||
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
|
||||
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
|
||||
|
||||
updateDate();
|
||||
}
|
||||
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f);
|
||||
|
||||
int red = Color.red(primaryColor);
|
||||
int green = Color.green(primaryColor);
|
||||
int blue = Color.blue(primaryColor);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
colors = new int[3];
|
||||
colors[0] = Color.argb(16, 255, 255, 255);
|
||||
colors[1] = Color.argb(128, red, green, blue);
|
||||
colors[2] = primaryColor;
|
||||
textColor = Color.rgb(255, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
colors = new int[3];
|
||||
colors[0] = Color.argb(25, 0, 0, 0);
|
||||
colors[1] = Color.argb(127, red, green, blue);
|
||||
colors[2] = primaryColor;
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
{
|
||||
pTextHeader = new Paint();
|
||||
pTextHeader.setTextAlign(Align.LEFT);
|
||||
pTextHeader.setAntiAlias(true);
|
||||
|
||||
pSquareBg = new Paint();
|
||||
pSquareBg.setColor(primaryColor);
|
||||
|
||||
pSquareFg = new Paint();
|
||||
pSquareFg.setColor(Color.WHITE);
|
||||
pSquareFg.setAntiAlias(true);
|
||||
pSquareFg.setTextAlign(Align.CENTER);
|
||||
}
|
||||
|
||||
protected void fetchData()
|
||||
{
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if(habit == null)
|
||||
{
|
||||
checkmarks = new int[0];
|
||||
return;
|
||||
}
|
||||
|
||||
checkmarks = habit.checkmarks.getAllValues();
|
||||
}
|
||||
|
||||
updateDate();
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
Random random = new Random();
|
||||
checkmarks = new int[100];
|
||||
|
||||
for(int i = 0; i < 100; i++)
|
||||
if(random.nextFloat() < 0.3) checkmarks[i] = 2;
|
||||
|
||||
for(int i = 0; i < 100 - 7; i++)
|
||||
{
|
||||
int count = 0;
|
||||
for (int j = 0; j < 7; j++)
|
||||
if(checkmarks[i + j] != 0)
|
||||
count++;
|
||||
|
||||
if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
|
||||
}
|
||||
}
|
||||
|
||||
private String previousMonth;
|
||||
private String previousYear;
|
||||
private boolean justPrintedYear;
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
|
||||
|
||||
previousMonth = "";
|
||||
previousYear = "";
|
||||
justPrintedYear = false;
|
||||
|
||||
pTextHeader.setColor(textColor);
|
||||
|
||||
updateDate();
|
||||
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
|
||||
|
||||
for (int column = 0; column < nColumns - 1; column++)
|
||||
{
|
||||
drawColumn(canvas, baseLocation, currentDate, column);
|
||||
baseLocation.offset(columnWidth, - columnHeight);
|
||||
}
|
||||
|
||||
drawAxis(canvas, baseLocation);
|
||||
}
|
||||
|
||||
private void drawColumn(Canvas canvas, Rect location, GregorianCalendar date, int column)
|
||||
{
|
||||
drawColumnHeader(canvas, location, date);
|
||||
location.offset(0, columnWidth);
|
||||
|
||||
for (int j = 0; j < 7; j++)
|
||||
{
|
||||
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayWeekday))
|
||||
{
|
||||
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
|
||||
drawSquare(canvas, location, date, checkmarkOffset);
|
||||
}
|
||||
|
||||
date.add(Calendar.DAY_OF_MONTH, 1);
|
||||
location.offset(0, columnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSquare(Canvas canvas, Rect location, GregorianCalendar date,
|
||||
int checkmarkOffset)
|
||||
{
|
||||
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
||||
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
|
||||
|
||||
canvas.drawRect(location, pSquareBg);
|
||||
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
|
||||
canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg);
|
||||
}
|
||||
|
||||
private void drawAxis(Canvas canvas, Rect location)
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
location.offset(0, columnWidth);
|
||||
canvas.drawText(wdays[i], location.left + headerTextOffset,
|
||||
location.bottom - headerTextOffset, pTextHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean justSkippedColumn = false;
|
||||
|
||||
private void drawColumnHeader(Canvas canvas, Rect location, GregorianCalendar date)
|
||||
{
|
||||
String month = dfMonth.format(date.getTime());
|
||||
String year = dfYear.format(date.getTime());
|
||||
|
||||
if (!month.equals(previousMonth))
|
||||
{
|
||||
int offset = 0;
|
||||
if (justPrintedYear)
|
||||
{
|
||||
offset += columnWidth;
|
||||
justSkippedColumn = true;
|
||||
}
|
||||
|
||||
canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset,
|
||||
pTextHeader);
|
||||
|
||||
previousMonth = month;
|
||||
justPrintedYear = false;
|
||||
}
|
||||
else if (!year.equals(previousYear))
|
||||
{
|
||||
if(!justSkippedColumn)
|
||||
{
|
||||
canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
|
||||
previousYear = year;
|
||||
justPrintedYear = true;
|
||||
}
|
||||
|
||||
justSkippedColumn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
justSkippedColumn = false;
|
||||
justPrintedYear = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
createColors();
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitScoreView extends ScrollableDataView
|
||||
{
|
||||
public static final int BUCKET_SIZE = 7;
|
||||
public static final PorterDuffXfermode XFERMODE_CLEAR =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
||||
public static final PorterDuffXfermode XFERMODE_SRC =
|
||||
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
||||
|
||||
private Paint pGrid;
|
||||
private float em;
|
||||
private Habit habit;
|
||||
private SimpleDateFormat dfMonth;
|
||||
private SimpleDateFormat dfDay;
|
||||
|
||||
private Paint pText, pGraph;
|
||||
private RectF rect, prevRect;
|
||||
private int baseSize;
|
||||
private int paddingTop;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int nColumns;
|
||||
|
||||
private int textColor;
|
||||
private int dimmedTextColor;
|
||||
private int[] colors;
|
||||
private int[] scores;
|
||||
private int primaryColor;
|
||||
private boolean isBackgroundTransparent;
|
||||
|
||||
public HabitScoreView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
init();
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
createColors();
|
||||
fetchData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
dfDay = new SimpleDateFormat("d", Locale.getDefault());
|
||||
|
||||
rect = new RectF();
|
||||
prevRect = new RectF();
|
||||
}
|
||||
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if (isBackgroundTransparent)
|
||||
{
|
||||
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
|
||||
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
|
||||
|
||||
textColor = Color.argb(192, 255, 255, 255);
|
||||
dimmedTextColor = Color.argb(128, 255, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
dimmedTextColor = Color.argb(16, 0, 0, 0);
|
||||
}
|
||||
|
||||
colors = new int[4];
|
||||
|
||||
colors[0] = Color.rgb(230, 230, 230);
|
||||
colors[3] = primaryColor;
|
||||
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
|
||||
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
{
|
||||
pText = new Paint();
|
||||
pText.setAntiAlias(true);
|
||||
|
||||
pGraph = new Paint();
|
||||
pGraph.setTextAlign(Paint.Align.CENTER);
|
||||
pGraph.setAntiAlias(true);
|
||||
|
||||
pGrid = new Paint();
|
||||
pGrid.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
if(height < 9) height = 200;
|
||||
|
||||
baseSize = height / 9;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
nColumns = width / baseSize;
|
||||
paddingTop = (int) (baseSize * 0.15f);
|
||||
|
||||
pText.setTextSize(baseSize * 0.5f);
|
||||
pGraph.setTextSize(baseSize * 0.5f);
|
||||
pGraph.setStrokeWidth(baseSize * 0.1f);
|
||||
pGrid.setStrokeWidth(baseSize * 0.05f);
|
||||
em = pText.getFontSpacing();
|
||||
}
|
||||
|
||||
protected void fetchData()
|
||||
{
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if (habit == null)
|
||||
{
|
||||
scores = new int[0];
|
||||
return;
|
||||
}
|
||||
|
||||
scores = habit.scores.getAllValues(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
Random random = new Random();
|
||||
scores = new int[100];
|
||||
scores[0] = Score.MAX_SCORE / 2;
|
||||
|
||||
for(int i = 1; i < 100; i++)
|
||||
{
|
||||
int step = Score.MAX_SCORE / 10;
|
||||
scores[i] = scores[i - 1] + random.nextInt(step * 2) - step;
|
||||
scores[i] = Math.max(0, Math.min(Score.MAX_SCORE, scores[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
float lineHeight = pText.getFontSpacing();
|
||||
|
||||
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
||||
rect.offset(0, paddingTop);
|
||||
|
||||
drawGrid(canvas, rect);
|
||||
|
||||
String previousMonth = "";
|
||||
|
||||
pText.setTextAlign(Paint.Align.CENTER);
|
||||
pText.setColor(textColor);
|
||||
pGraph.setColor(primaryColor);
|
||||
prevRect.setEmpty();
|
||||
|
||||
long currentDate = DateHelper.getStartOfToday();
|
||||
|
||||
for(int k = 0; k < nColumns + getDataOffset() - 1; k++)
|
||||
currentDate -= 7 * DateHelper.millisecondsInOneDay;
|
||||
|
||||
for (int k = 0; k < nColumns; k++)
|
||||
{
|
||||
String month = dfMonth.format(currentDate);
|
||||
String day = dfDay.format(currentDate);
|
||||
|
||||
int score = 0;
|
||||
int offset = nColumns - k - 1 + getDataOffset();
|
||||
if(offset < scores.length) score = scores[offset];
|
||||
|
||||
double sRelative = ((double) score) / Score.MAX_SCORE;
|
||||
int height = (int) (columnHeight * sRelative);
|
||||
|
||||
rect.set(0, 0, baseSize, baseSize);
|
||||
rect.offset(k * columnWidth, paddingTop + columnHeight - height - columnWidth / 2);
|
||||
|
||||
if (!prevRect.isEmpty())
|
||||
{
|
||||
drawLine(canvas, prevRect, rect);
|
||||
drawMarker(canvas, prevRect);
|
||||
}
|
||||
|
||||
if (k == nColumns - 1) drawMarker(canvas, rect);
|
||||
|
||||
prevRect.set(rect);
|
||||
|
||||
rect.set(0, 0, columnWidth, columnHeight);
|
||||
rect.offset(k * columnWidth, paddingTop);
|
||||
|
||||
if (!month.equals(previousMonth))
|
||||
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
else
|
||||
canvas.drawText(day, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
|
||||
previousMonth = month;
|
||||
currentDate += 7 * DateHelper.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawGrid(Canvas canvas, RectF rGrid)
|
||||
{
|
||||
int nRows = 5;
|
||||
float rowHeight = rGrid.height() / nRows;
|
||||
|
||||
pText.setTextAlign(Paint.Align.LEFT);
|
||||
pText.setColor(textColor);
|
||||
pGrid.setColor(dimmedTextColor);
|
||||
|
||||
for (int i = 0; i < nRows; i++)
|
||||
{
|
||||
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em,
|
||||
rGrid.top + 1f * em, pText);
|
||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
|
||||
rGrid.offset(0, rowHeight);
|
||||
}
|
||||
|
||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
|
||||
}
|
||||
|
||||
private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo)
|
||||
{
|
||||
pGraph.setColor(primaryColor);
|
||||
canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(),
|
||||
pGraph);
|
||||
}
|
||||
|
||||
private void drawMarker(Canvas canvas, RectF rect)
|
||||
{
|
||||
rect.inset(columnWidth * 0.15f, columnWidth * 0.15f);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
|
||||
setModeOrColor(pGraph, XFERMODE_SRC, primaryColor);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
|
||||
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
|
||||
canvas.drawOval(rect, pGraph);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
pGraph.setXfermode(XFERMODE_SRC);
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
createColors();
|
||||
}
|
||||
|
||||
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
||||
{
|
||||
if(isBackgroundTransparent)
|
||||
p.setXfermode(mode);
|
||||
else
|
||||
p.setColor(color);
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Streak;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitStreakView extends ScrollableDataView
|
||||
{
|
||||
private Habit habit;
|
||||
private Paint pText, pBar;
|
||||
|
||||
private long[] startTimes;
|
||||
private long[] endTimes;
|
||||
private long[] lengths;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int headerHeight;
|
||||
private int nColumns;
|
||||
|
||||
private long maxStreakLength;
|
||||
private int[] colors;
|
||||
private SimpleDateFormat dfMonth;
|
||||
private Rect rect;
|
||||
private int baseSize;
|
||||
private int primaryColor;
|
||||
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private Paint pBarText;
|
||||
|
||||
public HabitStreakView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
init();
|
||||
}
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
|
||||
createColors();
|
||||
fetchData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
rect = new Rect();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
baseSize = height / 10;
|
||||
setScrollerBucketSize(baseSize);
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
headerHeight = baseSize;
|
||||
nColumns = width / baseSize - 1;
|
||||
|
||||
pText.setTextSize(baseSize * 0.5f);
|
||||
pBar.setTextSize(baseSize * 0.5f);
|
||||
}
|
||||
|
||||
private void createColors()
|
||||
{
|
||||
if(habit != null)
|
||||
this.primaryColor = habit.color;
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
|
||||
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
|
||||
}
|
||||
|
||||
int red = Color.red(primaryColor);
|
||||
int green = Color.green(primaryColor);
|
||||
int blue = Color.blue(primaryColor);
|
||||
|
||||
if(isBackgroundTransparent)
|
||||
{
|
||||
colors = new int[4];
|
||||
colors[3] = primaryColor;
|
||||
colors[2] = Color.argb(213, red, green, blue);
|
||||
colors[1] = Color.argb(170, red, green, blue);
|
||||
colors[0] = Color.argb(128, red, green, blue);
|
||||
textColor = Color.rgb(255, 255, 255);
|
||||
pBarText = pText;
|
||||
}
|
||||
else
|
||||
{
|
||||
colors = new int[4];
|
||||
colors[3] = primaryColor;
|
||||
colors[2] = Color.argb(192, red, green, blue);
|
||||
colors[1] = Color.argb(96, red, green, blue);
|
||||
colors[0] = Color.argb(32, 0, 0, 0);
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
pBarText = pBar;
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
{
|
||||
pText = new Paint();
|
||||
pText.setTextAlign(Paint.Align.CENTER);
|
||||
pText.setAntiAlias(true);
|
||||
|
||||
pBar = new Paint();
|
||||
pBar.setTextAlign(Paint.Align.CENTER);
|
||||
pBar.setAntiAlias(true);
|
||||
}
|
||||
|
||||
protected void fetchData()
|
||||
{
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if(habit == null)
|
||||
{
|
||||
startTimes = endTimes = lengths = new long[0];
|
||||
return;
|
||||
}
|
||||
|
||||
List<Streak> streaks = habit.streaks.getAll();
|
||||
int size = streaks.size();
|
||||
|
||||
startTimes = new long[size];
|
||||
endTimes = new long[size];
|
||||
lengths = new long[size];
|
||||
|
||||
int k = 0;
|
||||
for (Streak s : streaks)
|
||||
{
|
||||
startTimes[k] = s.start;
|
||||
endTimes[k] = s.end;
|
||||
lengths[k] = s.length;
|
||||
k++;
|
||||
|
||||
maxStreakLength = Math.max(maxStreakLength, s.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateRandomData()
|
||||
{
|
||||
int size = 30;
|
||||
|
||||
startTimes = new long[size];
|
||||
endTimes = new long[size];
|
||||
lengths = new long[size];
|
||||
|
||||
Random random = new Random();
|
||||
Long date = DateHelper.getStartOfToday();
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
int l = (int) Math.pow(2, random.nextFloat() * 5 + 1);
|
||||
|
||||
endTimes[i] = date;
|
||||
date -= l * DateHelper.millisecondsInOneDay;
|
||||
lengths[i] = (long) l;
|
||||
startTimes[i] = date;
|
||||
|
||||
maxStreakLength = Math.max(maxStreakLength, l);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
|
||||
float lineHeight = pText.getFontSpacing();
|
||||
float barHeaderOffset = lineHeight * 0.4f;
|
||||
|
||||
int nStreaks = startTimes.length;
|
||||
int start = nStreaks - nColumns - getDataOffset();
|
||||
|
||||
pText.setColor(textColor);
|
||||
|
||||
String previousMonth = "";
|
||||
|
||||
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
|
||||
{
|
||||
if(start + offset < 0) continue;
|
||||
String month = dfMonth.format(startTimes[start + offset]);
|
||||
|
||||
long l = lengths[offset + start];
|
||||
double lRelative = ((double) l) / maxStreakLength;
|
||||
|
||||
pBar.setColor(colors[(int) Math.floor(lRelative * 3)]);
|
||||
|
||||
int height = (int) (columnHeight * lRelative);
|
||||
rect.set(0, 0, columnWidth - 2, height);
|
||||
rect.offset(offset * columnWidth, headerHeight + columnHeight - height);
|
||||
|
||||
canvas.drawRect(rect, pBar);
|
||||
canvas.drawText(Long.toString(l), rect.centerX(), rect.top - barHeaderOffset, pBarText);
|
||||
|
||||
if (!month.equals(previousMonth))
|
||||
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
|
||||
previousMonth = month;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||
{
|
||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||
createColors();
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.helpers.ColorHelper;
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
|
||||
public class RingView extends View
|
||||
{
|
||||
|
||||
private int size;
|
||||
private int color;
|
||||
private float percentage;
|
||||
private Paint pRing;
|
||||
private float lineHeight;
|
||||
private String label;
|
||||
private RectF rect;
|
||||
|
||||
public RingView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
|
||||
this.size = (int) context.getResources().getDimension(R.dimen.small_square_size) * 4;
|
||||
this.label = DialogHelper.getAttribute(context, attrs, "label");
|
||||
this.color = ColorHelper.palette[7];
|
||||
this.percentage = 0.75f;
|
||||
init();
|
||||
}
|
||||
|
||||
public void setColor(int color)
|
||||
{
|
||||
this.color = color;
|
||||
pRing.setColor(color);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
public void setPercentage(float percentage)
|
||||
{
|
||||
this.percentage = percentage;
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
pRing = new Paint();
|
||||
pRing.setAntiAlias(true);
|
||||
pRing.setColor(color);
|
||||
pRing.setTextAlign(Paint.Align.CENTER);
|
||||
pRing.setTextSize(size * 0.2f);
|
||||
lineHeight = pRing.getFontSpacing();
|
||||
rect = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(size, size + (int) (2 * lineHeight));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
float thickness = size * 0.15f;
|
||||
|
||||
pRing.setColor(color);
|
||||
rect.set(0, 0, size, size);
|
||||
canvas.drawArc(rect, -90, 360 * percentage, true, pRing);
|
||||
|
||||
pRing.setColor(Color.rgb(230, 230, 230));
|
||||
canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing);
|
||||
|
||||
pRing.setColor(Color.WHITE);
|
||||
rect.inset(thickness, thickness);
|
||||
canvas.drawArc(rect, -90, 360, true, pRing);
|
||||
|
||||
pRing.setColor(Color.GRAY);
|
||||
pRing.setTextSize(size * 0.2f);
|
||||
canvas.drawText(String.format("%.0f%%", percentage * 100), rect.centerX(),
|
||||
rect.centerY() + lineHeight / 3, pRing);
|
||||
|
||||
pRing.setTextSize(size * 0.15f);
|
||||
canvas.drawText(label, size / 2, size + lineHeight * 1.2f, pRing);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.views;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
|
||||
public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener,
|
||||
ValueAnimator.AnimatorUpdateListener
|
||||
{
|
||||
|
||||
private int dataOffset;
|
||||
private int scrollerBucketSize;
|
||||
|
||||
private GestureDetector detector;
|
||||
private Scroller scroller;
|
||||
private ValueAnimator scrollAnimator;
|
||||
|
||||
public ScrollableDataView(Context context)
|
||||
{
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ScrollableDataView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context)
|
||||
{
|
||||
detector = new GestureDetector(context, this);
|
||||
scroller = new Scroller(context, null, true);
|
||||
scrollAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
scrollAnimator.addUpdateListener(this);
|
||||
}
|
||||
|
||||
protected abstract void fetchData();
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
return detector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
|
||||
{
|
||||
if(scrollerBucketSize == 0)
|
||||
return false;
|
||||
|
||||
if(Math.abs(dx) > Math.abs(dy))
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
|
||||
scroller.computeScrollOffset();
|
||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
||||
postInvalidate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
|
||||
{
|
||||
scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000,
|
||||
0, 0);
|
||||
invalidate();
|
||||
|
||||
scrollAnimator.setDuration(scroller.getDuration());
|
||||
scrollAnimator.start();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation)
|
||||
{
|
||||
if (!scroller.isFinished())
|
||||
{
|
||||
scroller.computeScrollOffset();
|
||||
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
|
||||
postInvalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public int getDataOffset()
|
||||
{
|
||||
return dataOffset;
|
||||
}
|
||||
|
||||
public void setScrollerBucketSize(int scrollerBucketSize)
|
||||
{
|
||||
this.scrollerBucketSize = scrollerBucketSize;
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.Image;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import org.isoron.helpers.DialogHelper;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class BaseWidgetProvider extends AppWidgetProvider
|
||||
{
|
||||
|
||||
private int width, height;
|
||||
|
||||
protected abstract int getDefaultHeight();
|
||||
|
||||
protected abstract int getDefaultWidth();
|
||||
|
||||
protected abstract PendingIntent getOnClickPendingIntent(Context context, Habit habit);
|
||||
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
protected abstract View buildCustomView(Context context, Habit habit);
|
||||
|
||||
public static String getHabitIdKey(long widgetId)
|
||||
{
|
||||
return String.format("widget-%06d-habit", widgetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleted(Context context, int[] appWidgetIds)
|
||||
{
|
||||
Context appContext = context.getApplicationContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
|
||||
for(Integer id : appWidgetIds)
|
||||
prefs.edit().remove(getHabitIdKey(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
|
||||
int appWidgetId, Bundle newOptions)
|
||||
{
|
||||
updateWidget(context, appWidgetManager, appWidgetId, newOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds)
|
||||
{
|
||||
for(int id : appWidgetIds)
|
||||
{
|
||||
Bundle options = null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
|
||||
options = manager.getAppWidgetOptions(id);
|
||||
|
||||
updateWidget(context, manager, id, options);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidget(Context context, AppWidgetManager manager, int widgetId, Bundle options)
|
||||
{
|
||||
updateWidgetSize(context, options);
|
||||
|
||||
Context appContext = context.getApplicationContext();
|
||||
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
|
||||
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L);
|
||||
if(habitId < 0) return;
|
||||
|
||||
Habit habit = Habit.get(habitId);
|
||||
View widgetView = buildCustomView(context, habit);
|
||||
measureCustomView(context, width, height, widgetView);
|
||||
|
||||
widgetView.setDrawingCacheEnabled(true);
|
||||
widgetView.buildDrawingCache(true);
|
||||
Bitmap drawingCache = widgetView.getDrawingCache();
|
||||
|
||||
remoteViews.setTextViewText(R.id.label, habit.name);
|
||||
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
|
||||
|
||||
//savePreview(context, widgetId, drawingCache);
|
||||
|
||||
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
|
||||
if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);
|
||||
|
||||
manager.updateAppWidget(widgetId, remoteViews);
|
||||
}
|
||||
|
||||
private void savePreview(Context context, int widgetId, Bitmap widgetCache)
|
||||
{
|
||||
try
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View view = inflater.inflate(getLayoutId(), null);
|
||||
|
||||
ImageView iv = (ImageView) view.findViewById(R.id.imageView);
|
||||
iv.setImageBitmap(widgetCache);
|
||||
|
||||
view.measure(width, height);
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
view.setDrawingCacheEnabled(true);
|
||||
view.buildDrawingCache();
|
||||
Bitmap previewCache = view.getDrawingCache();
|
||||
|
||||
String filename = String.format("%s/%d.png", context.getExternalCacheDir(), widgetId);
|
||||
Log.d("BaseWidgetProvider", String.format("Writing %s", filename));
|
||||
FileOutputStream out = new FileOutputStream(filename);
|
||||
|
||||
if(previewCache != null)
|
||||
previewCache.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
|
||||
out.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWidgetSize(Context context, Bundle options)
|
||||
{
|
||||
int maxWidth = getDefaultWidth();
|
||||
int minWidth = getDefaultWidth();
|
||||
int maxHeight = getDefaultHeight();
|
||||
int minHeight = getDefaultHeight();
|
||||
|
||||
if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
{
|
||||
maxWidth = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
|
||||
maxHeight = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
|
||||
minWidth = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
|
||||
minHeight = (int) DialogHelper.dpToPixels(context,
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
|
||||
}
|
||||
|
||||
width = maxWidth;
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
private void measureCustomView(Context context, int w, int h, View customView)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View entireView = inflater.inflate(getLayoutId(), null);
|
||||
|
||||
int specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
|
||||
int specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
|
||||
|
||||
entireView.measure(specWidth, specHeight);
|
||||
entireView.layout(0, 0, entireView.getMeasuredWidth(), entireView.getMeasuredHeight());
|
||||
|
||||
View imageView = entireView.findViewById(R.id.imageView);
|
||||
w = imageView.getMeasuredWidth();
|
||||
h = imageView.getMeasuredHeight();
|
||||
|
||||
specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
|
||||
specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
|
||||
customView.measure(specWidth, specHeight);
|
||||
customView.layout(0, 0, customView.getMeasuredWidth(), customView.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.HabitBroadcastReceiver;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.CheckmarkView;
|
||||
|
||||
public class CheckmarkWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
CheckmarkView view = new CheckmarkView(context);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
|
||||
{
|
||||
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_checkmark;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.isoron.uhabits.MainActivity;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.widgets.BaseWidgetProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HabitPickerDialog extends Activity implements AdapterView.OnItemClickListener
|
||||
{
|
||||
|
||||
private Integer widgetId;
|
||||
private ArrayList<Long> habitIds;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.widget_configure_activity);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras != null) widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID);
|
||||
|
||||
ListView listView = (ListView) findViewById(R.id.listView);
|
||||
|
||||
habitIds = new ArrayList<>();
|
||||
ArrayList<String> habitNames = new ArrayList<>();
|
||||
|
||||
List<Habit> habits = Habit.getAll(false);
|
||||
for(Habit h : habits)
|
||||
{
|
||||
habitIds.add(h.getId());
|
||||
habitNames.add(h.name);
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, habitNames);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setOnItemClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Long habitId = habitIds.get(position);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
prefs.edit().putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId).commit();
|
||||
|
||||
MainActivity.updateWidgets(this);
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
|
||||
setResult(RESULT_OK, resultValue);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.HabitHistoryView;
|
||||
|
||||
public class HistoryWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitHistoryView view = new HabitHistoryView(context, null);
|
||||
view.setHabit(habit);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.HabitScoreView;
|
||||
|
||||
public class ScoreWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitScoreView view = new HabitScoreView(context, null);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/* Copyright (C) 2016 Alinson Santos Xavier
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.widgets;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.views.HabitStreakView;
|
||||
|
||||
public class StreakWidgetProvider extends BaseWidgetProvider
|
||||
{
|
||||
@Override
|
||||
protected View buildCustomView(Context context, Habit habit)
|
||||
{
|
||||
HabitStreakView view = new HabitStreakView(context, null);
|
||||
view.setIsBackgroundTransparent(true);
|
||||
view.setHabit(habit);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultHeight()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultWidth()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutId()
|
||||
{
|
||||
return R.layout.widget_graph;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 410 B |
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 244 B |
|
Before Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 243 B |
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
<gradient
|
||||
android:endColor="#00000000"
|
||||
android:gradientRadius="50%p"
|
||||
android:startColor="#50000000"
|
||||
android:type="radial" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="5dp"
|
||||
android:left="2dp"
|
||||
android:right="4dp"
|
||||
android:top="1dp">
|
||||
<shape android:shape="oval" >
|
||||
<solid android:color="#ffffff" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<shape android:shape="oval" >
|
||||
<gradient
|
||||
android:endColor="#00000000"
|
||||
android:gradientRadius="50%p"
|
||||
android:startColor="#50000000"
|
||||
android:type="radial" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="5dp"
|
||||
android:left="2dp"
|
||||
android:right="4dp"
|
||||
android:top="1dp">
|
||||
<shape android:shape="oval" >
|
||||
<solid android:color="#20000000" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -1,8 +0,0 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:id="@android:id/mask">
|
||||
<color android:color="@android:color/white" />
|
||||
</item>
|
||||
|
||||
</ripple>
|
||||
@@ -1,5 +0,0 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
|
||||
<item android:drawable="@color/white" />
|
||||
</ripple>
|
||||
|
Before Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 220 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 426 B |