Compare commits
959 Commits
v2.0.0-alp
...
516bf394f8
| Author | SHA1 | Date | |
|---|---|---|---|
| 516bf394f8 | |||
| 2816b7c3d0 | |||
| a9acbd6cab | |||
| e121f46b61 | |||
| d57de9d10c | |||
| e4348a2144 | |||
| e608c6ea62 | |||
| 5403b6bd51 | |||
| a6cf43dbca | |||
| 074627f6e1 | |||
|
|
96e20f751f | ||
| 0daa4f6a2f | |||
| 035b392ece | |||
| 648c7277cf | |||
| 5006f5128b | |||
| 97b98a872d | |||
| 862a851e1c | |||
| 804030f5c0 | |||
| 08ab3c22ce | |||
| b58f836d8e | |||
| 9ed4630f9b | |||
| 70dab74528 | |||
| 7e5d2fa207 | |||
| 0e432fb332 | |||
| 897a236501 | |||
| 0cccecec77 | |||
| f1ed875256 | |||
| e82bd47aab | |||
| e9517f7378 | |||
| 12cc70a51a | |||
| fa670b19b7 | |||
| 45b100aad9 | |||
| 3c0c0b77ff | |||
| 66fa56ea62 | |||
| 951dabea8b | |||
| 76b9dd8bd9 | |||
| f68510f860 | |||
| 245b0eb4d6 | |||
| 4a0599fce4 | |||
| abbfe87260 | |||
| 3330014fa9 | |||
|
|
cc720e3dcb | ||
|
|
6e3d06cff9 | ||
|
|
d458cbd47a | ||
|
|
74ce269446 | ||
|
|
9eb8624863 | ||
|
|
c4bc301fb2 | ||
|
|
107c898f51 | ||
|
|
4a7d7ef490 | ||
|
|
13ecc2a386 | ||
|
|
2296a49999 | ||
|
|
303020a8c0 | ||
|
|
37219cb13f | ||
|
|
62d9d29e91 | ||
| 451b536e71 | |||
| 14dbf90c23 | |||
| 280a5ddceb | |||
| 22331ed364 | |||
| d0a45eb523 | |||
|
|
fec73af665 | ||
|
fc9cc423d0
|
|||
|
e7165d993f
|
|||
|
732ec1c70a
|
|||
|
e823cd5758
|
|||
| 46fe683d71 | |||
|
7f6248123c
|
|||
|
2024277ebe
|
|||
| c216fb01d6 | |||
|
7cb32f486b
|
|||
|
093591fbaf
|
|||
|
985234cdf3
|
|||
|
06090e238a
|
|||
|
e48452f724
|
|||
|
|
936986e110 | ||
|
|
4b3910aea8 | ||
|
|
10074ded32 | ||
|
|
1280e798d2
|
||
|
|
b09306e793
|
||
|
|
e30636a447
|
||
|
|
ad8738180c
|
||
|
|
08410c59d0
|
||
|
|
ab86cee70b | ||
|
|
3a0603605b
|
||
|
|
6a78b4d853 | ||
|
|
fe43b1435d | ||
|
|
12503b8a6d | ||
|
|
ef7f78bff0 | ||
|
|
53c208ded5 | ||
|
|
1bdc83e92f | ||
|
|
680c1cdc76 | ||
|
|
80916bac50 | ||
|
|
a5e3e9b3cf | ||
|
248ba50a8e
|
|||
|
|
45a82b3c2d
|
||
|
770d1293dc
|
|||
|
d10538e720
|
|||
|
02e9e2384e
|
|||
|
|
b627ff4413
|
||
|
|
0683ea43f4
|
||
|
08f77a5cae
|
|||
|
27df792775
|
|||
|
|
800f92f255
|
||
|
|
e06ed3ed7d | ||
|
|
3bb119c6ed | ||
|
|
0762699a86 | ||
|
|
8837326d44 | ||
|
|
481a3d5784 | ||
|
8eff782f54
|
|||
|
c590734c42
|
|||
|
7e7f68282b
|
|||
|
4bd5cee17b
|
|||
|
b2421dc8b1
|
|||
|
93363bff96
|
|||
|
d01044b203
|
|||
|
70fe513e52
|
|||
|
dee93fde8f
|
|||
|
f0ce05e06e
|
|||
|
4975ba2752
|
|||
|
ed8c60e52f
|
|||
|
|
7735247521 | ||
|
|
b32a6ad1b8 | ||
|
|
21a512ae71 | ||
|
|
b0944d3f1c | ||
|
|
8a9b719c50 | ||
|
|
ef7454ae75 | ||
|
|
b51f6abfce | ||
|
|
a013635224 | ||
|
|
a9f028a34b | ||
|
|
3b0fba12f5 | ||
|
|
4139f09fb7 | ||
|
|
248ff2ec62 | ||
|
|
cc7178eb21 | ||
|
6116ef9450
|
|||
|
8801960615
|
|||
|
b0a4284b66
|
|||
|
|
334dabb407 | ||
|
|
b2fc79a3ab | ||
|
|
f0e8643e6b | ||
|
|
5cd616f967 | ||
|
|
b53ef758ec | ||
|
|
5add03bf23 | ||
|
|
b465ee588b | ||
|
|
b9253d41ea | ||
|
|
ca0a9dd85f | ||
|
|
9951525cbe | ||
|
|
98b2c9cce2 | ||
|
|
19de2a2d1c | ||
|
|
80f783b669 | ||
|
|
761fe59c5e | ||
|
|
f58d8a52ff | ||
|
|
f4e5b68258 | ||
|
|
5df3ee0d61 | ||
|
|
25cff3d9b0 | ||
|
|
9f66a27b82 | ||
|
|
4bbb20e18e | ||
|
|
4f9ab6d263 | ||
|
|
af21fd25db | ||
|
|
35097e1263 | ||
|
|
8e4274d923 | ||
|
|
12649141b1 | ||
|
|
0526d37fbd | ||
|
|
b083899ec8 | ||
|
|
6b793c7c16 | ||
|
|
11ca993a75 | ||
|
|
7348ddeffa | ||
|
88df8d2552
|
|||
|
d4f4f8b4a9
|
|||
|
9ca1aa911a
|
|||
|
ba57ebad31
|
|||
|
|
e4d2c93a1d | ||
|
|
07065a60ad | ||
|
|
916cd76be1 | ||
|
8b55ffb147
|
|||
|
727e88b7b1
|
|||
|
f70d33878c
|
|||
|
6a55d3c01a
|
|||
|
69b5ed3a6d
|
|||
|
8b2adbf301
|
|||
|
88cc3a2a12
|
|||
|
26526a71a9
|
|||
|
11eb3713e5
|
|||
|
1df9cc7664
|
|||
|
|
499eb467cb | ||
|
|
9d4df73c56 | ||
|
|
d0f32dfa0a | ||
|
|
c609eceefc | ||
|
|
eb68220b8d | ||
|
|
f1a003fabf | ||
|
b76da35752
|
|||
|
abead88ceb
|
|||
|
908eb4ac99
|
|||
|
|
856a0726f7 | ||
|
|
df97b1fd4f | ||
|
71a05d598a
|
|||
|
2131fb3a3d
|
|||
|
1470dcd560
|
|||
| dc678e59df | |||
|
|
d95084500f | ||
|
6f7215b46f
|
|||
|
|
c423d2b3ca | ||
|
|
758fc56277 | ||
|
|
c7d1e92cae | ||
|
|
d24dcbf2ca | ||
|
|
579b33cc78 | ||
|
|
1a3e6315a1 | ||
|
|
7f4d06d15d | ||
|
|
b8033a6012 | ||
|
471f977209
|
|||
|
2ba5f5fb98
|
|||
|
4de67bd27a
|
|||
|
0bb82a48a5
|
|||
|
d5a5273607
|
|||
|
404671546c
|
|||
|
a94c6e8b9f
|
|||
|
da09df2dd1
|
|||
|
40a4d254f5
|
|||
|
177d01edd9
|
|||
|
9f5da7b4fe
|
|||
|
e42d41ef30
|
|||
|
5498ff8a87
|
|||
|
20dcc7929b
|
|||
|
1283cf979d
|
|||
|
0a493ff065
|
|||
|
16cd249bae
|
|||
|
d136572960
|
|||
|
318aa3c821
|
|||
|
73712e0d10
|
|||
| bf504641c6 | |||
|
ec1f0c5356
|
|||
|
2154d8c192
|
|||
|
961fb7618f
|
|||
|
11f726064a
|
|||
|
4c5a722dc5
|
|||
| ee39ff0eda | |||
| abced92a07 | |||
|
|
eeacc5eef8 | ||
|
|
16c65f19fd | ||
|
|
6a9c3a36eb | ||
|
|
99ccb44ad3 | ||
| fc402fd81b | |||
| f7c6bc716c | |||
| 2535347d5a | |||
|
|
8c0655c352 | ||
|
|
335f8c32fd | ||
|
|
f77c064722 | ||
|
|
8b0b757f04 | ||
| 13af054214 | |||
| fcbb586e80 | |||
| 458c9f3b15 | |||
| dfa74960b3 | |||
| f082842fbe | |||
| d4d818a085 | |||
|
|
f074d0331d | ||
|
|
f94bc62a94 | ||
|
|
428bf42e79 | ||
| b0097fa45e | |||
|
|
15fa1fea8c | ||
| 31368cff45 | |||
| b44dd96dd3 | |||
| 838e13f30c | |||
|
|
08fab0cd8d | ||
|
|
a142685d2e | ||
| a7a95f2030 | |||
|
|
d3c90481be | ||
| f9bb0d7d7b | |||
| f23a1bedee | |||
| dcf31ba115 | |||
| 0eae43fe55 | |||
| c0fcd4e763 | |||
| 79e2402c9d | |||
|
|
5409a324e8 | ||
|
|
504362e680 | ||
|
|
0ce2f8fae2 | ||
| 2fc6c67432 | |||
| 459cf02642 | |||
| 44cb64601f | |||
| 53c270ee12 | |||
| e4b16f6d59 | |||
| 3021e408a7 | |||
| 7649119db7 | |||
| b0a4f26e7a | |||
|
|
dd47d4cf08 | ||
|
|
8912a9d73c | ||
|
|
9bd1c6f685 | ||
|
|
e15e06034c | ||
|
|
a6180a5049 | ||
|
|
7c69b17e77 | ||
|
|
602a40a532 | ||
|
|
e00998f913 | ||
|
|
af5914c2db | ||
|
|
6c5f70638a | ||
|
|
ba88552919 | ||
|
|
8c90c4f68d | ||
|
|
08eb1a600d | ||
|
|
8ea0480d4a | ||
|
|
36ee39589e | ||
|
|
ac7a721940 | ||
|
|
b36ca8673a | ||
|
|
7cfac486f9 | ||
|
|
21dd413ab5 | ||
|
|
39e10638b5 | ||
| 0a95b6d2a0 | |||
| e6bcbb39ff | |||
| 90de1f3723 | |||
| 68740b4043 | |||
| b66a6ff717 | |||
| 7c1a91e35a | |||
| 366e9af167 | |||
| 03b02aaa06 | |||
|
|
180cf25ad3 | ||
| 3490cd183a | |||
|
|
0c292d1eaa | ||
| 0b256cb2c0 | |||
| cc03c48648 | |||
| 53e7ef2918 | |||
| 9609bee0f7 | |||
| 5b23a3f960 | |||
| d2d45991b0 | |||
| 25aeafb759 | |||
| 9045ae5c24 | |||
| 555873354c | |||
| 2a012619a7 | |||
| d1de3a852b | |||
| f04e37e905 | |||
| 825a5f2cb9 | |||
| 0de6896691 | |||
| 7187214282 | |||
|
|
9d4161a255 | ||
|
|
d82a3c145d | ||
|
|
a6cbd44e42 | ||
|
|
45c62b4ab2 | ||
|
|
d53312d261 | ||
|
|
c453810785 | ||
|
|
999057300b | ||
|
|
2245347e28 | ||
|
|
b79f7850ed | ||
|
|
4469f86e46 | ||
|
|
bdeddb149e | ||
|
|
3b12ec4bfe | ||
| ca4618579e | |||
| 2f13aba22c | |||
| dba5912ecd | |||
| 88b8663484 | |||
| 609886fd09 | |||
| a4db997e06 | |||
|
|
090216ccb7 | ||
|
|
472092d1bf | ||
|
|
6d3791de31 | ||
|
|
24a99d5791 | ||
|
|
6c631b1cf6 | ||
|
|
5d7677b354 | ||
|
|
22820f4f24 | ||
| 1d3bd48535 | |||
|
|
694446b7e3 | ||
|
|
20ae9d247e | ||
|
|
1943fac610 | ||
|
|
a07a50e635 | ||
|
|
8ccb9bbab1 | ||
|
|
743b8d26ad | ||
|
|
c47bd4c328 | ||
|
|
d84abc3a6a | ||
|
|
4ae85f1ec0 | ||
|
|
79d40d1d79 | ||
|
|
1902b8821e | ||
|
|
075542d605 | ||
|
|
0b5894ee6d | ||
|
|
92fb9dbdb6 | ||
|
|
1860abf532 | ||
|
|
3f4780c9fb | ||
|
|
d5d6e4616e | ||
|
|
18e267053d | ||
| 7ba9a7e4e7 | |||
| 5b8a7c39e2 | |||
| d40a5a89cd | |||
| 308d558347 | |||
| 9770ce187a | |||
| 535bc03b70 | |||
| 642e45af89 | |||
| 56d2307b75 | |||
| d875af8a8e | |||
| feeb4f057d | |||
| 1ad5c6b896 | |||
| baee3b9f86 | |||
| e6167baab1 | |||
|
|
1c15e7742e | ||
|
|
072ba63789 | ||
|
|
0fa0daa058 | ||
|
|
5a5ed3d631 | ||
|
|
3dfa376f59 | ||
|
|
7bf74634bb | ||
|
|
fc645a81bc | ||
|
|
35365bbdf3 | ||
|
|
779ef5dbee | ||
|
|
e82994c76b | ||
|
|
4e1d01d8d1 | ||
|
|
5de0fc86e5 | ||
| e26b643423 | |||
| 621534d610 | |||
| a01300e9c6 | |||
| ecb8ce105a | |||
| 32ef3c14f7 | |||
| 4972257635 | |||
| c98cb50baa | |||
| c331f34fa9 | |||
| a1aea532b5 | |||
| 43489aeb4c | |||
| 990c85aedd | |||
|
|
64337b9bee | ||
|
|
8bdfaa2434 | ||
|
|
5f6060858d | ||
| b62e436054 | |||
| bf63b4dbcf | |||
| a82d940bcc | |||
| ba59dc7ca9 | |||
| 181290a0f3 | |||
|
|
d553c2f3f2 | ||
|
|
7776093217 | ||
|
|
b27f3f8540 | ||
| eb041bf6b2 | |||
|
|
e3c53bf07f | ||
|
|
9ddab6ee59 | ||
|
|
2615795402 | ||
|
|
6531445d7f | ||
|
|
4fbf8a8ca2 | ||
|
|
707b2b4380 | ||
|
|
aae85c1170 | ||
|
|
c12a6c6a4d | ||
|
|
b15c02adbf | ||
|
|
9e24128675 | ||
|
|
66c61e2e6c | ||
|
|
7bddfbe5a7 | ||
|
|
8036b10ee6 | ||
|
|
71f400f587 | ||
|
|
79e302f922 | ||
|
|
af7f60fc4d | ||
|
|
7cc4b66dfd | ||
|
|
a9fddf9963 | ||
|
|
36c1504c6a | ||
|
|
d644170141 | ||
|
|
d38f83e961 | ||
|
|
c50c5af497 | ||
|
|
fa3774a32b | ||
|
|
fd124f2a6c | ||
|
|
265b65eb8a | ||
|
|
4c269c55d2 | ||
|
|
c03305120e | ||
|
|
29615b670b | ||
|
|
6ab4a696b6 | ||
|
|
23479c7765 | ||
|
|
6d98f7aafa | ||
| 75078ed52b | |||
| 2a0afedb1d | |||
| 66a2b41250 | |||
|
|
d6a7fa3d7a | ||
|
|
07e55f1c76 | ||
| 4ee5dd910b | |||
| 87f071b5b4 | |||
| bb0b5e8adf | |||
|
|
c79d1e82a5 | ||
|
|
4aebeedec6 | ||
|
|
7de94f2caf | ||
|
|
17ed85fc1b | ||
|
|
4355fb4d68 | ||
|
|
508200abeb | ||
|
|
a29943e783 | ||
|
|
3e6a9181d6 | ||
| 1fe3a3d1ca | |||
|
|
b2951a3475 | ||
|
|
9d3c63cf62 | ||
|
|
65d237254c | ||
|
|
fe1d5c66cb | ||
|
|
113a5028af | ||
|
|
1a56260757 | ||
|
|
697fffbc99 | ||
|
|
804edfa64e | ||
| 2ab6c396d0 | |||
| a55f467339 | |||
|
|
cf682f68c9 | ||
| 0e988e746c | |||
|
|
f119cbf8e7 | ||
|
|
056f5f6fce | ||
|
|
42f6125d5e | ||
|
|
3e20fc4d1d | ||
|
|
1f763feb69 | ||
|
|
6e7ad329fe | ||
|
|
5cb241475d | ||
|
|
27e76c7243 | ||
|
|
576ad04064 | ||
|
|
5f8187ef6d | ||
|
|
f16f919e27 | ||
|
|
736bb8a75e | ||
|
|
de9ad6d4a4 | ||
|
|
1d37ce54ea | ||
|
|
f88f1cfb54 | ||
|
|
fc1478645b | ||
|
|
ffab001b09 | ||
|
|
a58a8005e1 | ||
|
|
c884ada187 | ||
|
|
e3d46ad5a0 | ||
|
|
f4a2c03216 | ||
|
|
f2b8f2f98d | ||
|
|
2c5fd87a2a | ||
|
|
39768f7f04 | ||
|
|
cc3e1ced15 | ||
|
|
2e26cc104e | ||
|
|
42fd0926ef | ||
| ec202aa9a7 | |||
|
|
1fb56c8777 | ||
|
|
a5d4a37da8 | ||
|
|
4804a48549 | ||
|
|
c892a845b4 | ||
|
|
e98064b6a5 | ||
| 57f5f6ed5b | |||
| 79f5b8b7e8 | |||
| f15c660d33 | |||
| 1866743c47 | |||
| 6b9a7917b4 | |||
| 2a5725f382 | |||
| edeabb6ee3 | |||
| 24a9fbe414 | |||
| e5b8c4c3c4 | |||
|
|
a781a1f947 | ||
|
|
13e57b5026 | ||
|
|
f8c7abfff4 | ||
|
|
7fe3ce970c | ||
| 9c395243f4 | |||
|
|
b9eb244b0b | ||
|
|
420a99f1cf | ||
| e756a639ae | |||
|
|
91ff5f7a0c | ||
|
|
67b55a4ecf | ||
|
|
a5ae2eaa63 | ||
|
|
0bc2a8b6d4 | ||
|
|
95a1786c4a | ||
|
|
caa1c9d72e | ||
|
|
a7afe0b309 | ||
|
|
9c03174eef | ||
|
|
da02926fa6 | ||
|
|
250dabfe58 | ||
|
|
0bba3b76bc | ||
|
|
c2479278ba | ||
|
|
499a403a06 | ||
|
|
af5d622339 | ||
|
|
5eeb54bc47 | ||
|
|
e09e899aad | ||
| 69f0fc6c3a | |||
| 705dfb9cbd | |||
| d0ef749f19 | |||
| b54711243f | |||
| 37f03aca37 | |||
|
|
271de59a94 | ||
|
|
0ab55f6f5a | ||
|
|
43921721d4 | ||
|
|
33c88cded3 | ||
|
|
aecce891ea | ||
|
|
2ea98a7756 | ||
|
|
e667872d83 | ||
|
|
3602a614c4 | ||
|
|
d8c5f4d93c | ||
|
|
c9f4df9dae | ||
|
|
feb384bca6 | ||
|
|
71e9160460 | ||
|
|
64966d3c86 | ||
|
|
4787df4074 | ||
| 92291fd919 | |||
| be51538704 | |||
| 89b24911ba | |||
| 1cf71b3973 | |||
| 2fe3b15806 | |||
| 33468bfc1c | |||
|
|
5908692a5c | ||
|
|
693dce8719 | ||
| b232827dfd | |||
|
|
02f9f411ce | ||
| bbf9da44e1 | |||
|
|
ee896fb4f9 | ||
|
|
4d7d8b6206 | ||
|
|
55b841a8b4 | ||
| 7fac86b617 | |||
|
|
c31d42be2d | ||
| 31c09b9c0b | |||
|
|
db91dce57f | ||
| 12c76245e6 | |||
| 2163a2b93b | |||
| afad56ab91 | |||
| ece1b93f8d | |||
| 10416e40fa | |||
| 88f8581acc | |||
| b33dd2a994 | |||
| d87961d800 | |||
| 38d2606d6d | |||
| 0a91c097e8 | |||
|
|
b1c53bd820 | ||
| c973f93424 | |||
| fcadbe7c38 | |||
| 59a4d7552c | |||
| 9d7840bdd1 | |||
| 89e3dd7655 | |||
| df56608a5d | |||
| 3489f176d6 | |||
| 75fdd9d2b1 | |||
| dd1999681f | |||
|
|
32db4e363b | ||
|
|
004bb8d71c | ||
|
|
21a1e88c47 | ||
|
|
86fb718896 | ||
|
|
a4e9b2f874 | ||
|
|
ac924470b8 | ||
| 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 |
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report something broken in the app
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
<!--
|
||||
Please use the template below for your bug report. Issues that do not follow this
|
||||
template, or that do not contain all necessary information (namely, description of
|
||||
the problem, steps to reproduce, phone, phone operating system, and app version)
|
||||
will be closed without further consideration.
|
||||
-->
|
||||
|
||||
## Pre-submission checklist
|
||||
- [ ] I am submitting a bug report, not a feature request.
|
||||
- [ ] I 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
|
||||
|
||||
## System information
|
||||
- Phone: [e.g. Google Pixel 4]
|
||||
- Phone Operating System: [e.g. Android 10]
|
||||
- App version: [e.g. 2.0.2]
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
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
|
||||
61
.github/workflows/main.yml
vendored
@@ -1,50 +1,31 @@
|
||||
name: Build & Test
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
Test:
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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: Build APK & Run small tests
|
||||
run: android/build.sh build
|
||||
- name: Run Android tests
|
||||
run: ./build.sh android-tests-parallel 28 29 30 32 33 34
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debug-apk
|
||||
path: android/build/*apk
|
||||
|
||||
- name: Upload build outputs
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: android/uhabits-android/build/outputs/
|
||||
path: |
|
||||
build/*log
|
||||
uhabits-android/build/outputs
|
||||
|
||||
test:
|
||||
needs: build
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [23, 24, 25, 26, 27, 28, 29]
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Download previous build folder
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: build
|
||||
path: android/uhabits-android/build/outputs/
|
||||
|
||||
- name: Run medium tests
|
||||
uses: ReactiveCircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
script: android/build.sh medium-tests
|
||||
|
||||
42
.github/workflows/publish.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: Build, Test & Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install GPG
|
||||
uses: olafurpg/setup-gpg@v2
|
||||
- name: Decrypt secrets
|
||||
env:
|
||||
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
|
||||
run: .secret/decrypt.sh
|
||||
- name: Install Java Development Kit 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build APK & Run small tests
|
||||
env:
|
||||
RELEASE: 1
|
||||
run: android/build.sh build
|
||||
- name: Run medium tests
|
||||
uses: ReactiveCircus/android-emulator-runner@v2.2.0
|
||||
env:
|
||||
RELEASE: 1
|
||||
with:
|
||||
api-level: 29
|
||||
script: android/build.sh medium-tests
|
||||
- name: Upload build to GitHub
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Build
|
||||
path: android/uhabits-android/build/outputs/
|
||||
- name: Upload APK to Google Play
|
||||
run: cd android && ./gradlew publishReleaseApk
|
||||
7
.gitignore
vendored
@@ -12,12 +12,9 @@
|
||||
.idea
|
||||
.secret
|
||||
build
|
||||
build/
|
||||
captures
|
||||
local.properties
|
||||
node_modules
|
||||
*xcuserdata*
|
||||
*.sketch
|
||||
/design
|
||||
/releases
|
||||
/screenshots
|
||||
crowdin.yml
|
||||
kotlin-js-store
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/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
537
CHANGELOG.md
@@ -1,235 +1,436 @@
|
||||
# Changelog
|
||||
|
||||
### 2.0.0 (TBD)
|
||||
## [2.3.1] -- 2025-08-13
|
||||
|
||||
* **New Features:**
|
||||
* Track numerical habits (@iSoron, @namnl)
|
||||
* Skip days without breaking streak (@KristianTashkov)
|
||||
* Sort habits by status (@hiqua)
|
||||
* Sort habits in reverse order (@iSoron)
|
||||
* Add notes to habits (@recheej)
|
||||
* Improve readibility of charts (@chennemann)
|
||||
* Delay new day until 3am (@KristianTashkov)
|
||||
* Export backups daily (@iSoron)
|
||||
* **Bug fixes:**
|
||||
* Reset chart offset when switching scale (@alxmjo)
|
||||
* Don't show reminders from archived habits (@KristianTashkov)
|
||||
* Lapses on non-daily habits decrease the score too much (@iSoron)
|
||||
* Update widgets at midnight (@KristianTashkov)
|
||||
* **Refactoring:**
|
||||
* Convert files to Kotlin (@olegivo)
|
||||
### Changed
|
||||
|
||||
### 1.8.10 (Nov 26, 2020)
|
||||
- Add notes to exported CSV files (@iSoron)
|
||||
|
||||
* Update translations
|
||||
### Fixed
|
||||
|
||||
### 1.8.9 (Nov 18, 2020)
|
||||
- Prevent some views from being obscured by system UI (@iSoron, #2171)
|
||||
- Disable confetti if animations are disabled globally (@iSoron, #2170)
|
||||
- Make symbols easier to distinguish in "pure black" dark mode (powerjungle, #2136)
|
||||
- Trim unit labels when necessary (@hiqua, @iSoron, #2158)
|
||||
|
||||
* 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
|
||||
## [2.3.0] -- 2025-06-23
|
||||
|
||||
### 1.8.8 (June 21, 2020)
|
||||
### Added
|
||||
|
||||
* Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably.
|
||||
* Fix crash when saving habit
|
||||
- Add support for Android 15 and 16 (@iSoron)
|
||||
- Show confetti animation (@gokulk16, @iSoron, #1743)
|
||||
- Show streaks for measurable habits (@teckwarz, #2059)
|
||||
- Allow user to unset measurable habits (@leontodd, @kalina559, #1899, #2109)
|
||||
|
||||
### 1.8.0 (Jan 1, 2020)
|
||||
### Changed
|
||||
|
||||
* 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.
|
||||
- Change background widget color for habits with implicit checks (@wobbba, #1915)
|
||||
|
||||
### 1.7.11 (Aug 10, 2019)
|
||||
### Fixed
|
||||
|
||||
* Fix bug that produced corrupted CSV files in some countries
|
||||
- Fix notification when goal type is set to maximum (@manish99verma, #1931)
|
||||
- Never mark "at most" habits as completed for the day (@kalina559, #2077)
|
||||
- Increase minimum widget size (@iSoron, #2118)
|
||||
- Improve Gradle configuration (@jimlyas, #2108)
|
||||
|
||||
### 1.7.10 (June 15, 2019)
|
||||
## [2.2.0] -- 2024-01-30
|
||||
|
||||
* Fix bug that prevented some devices from showing notifications.
|
||||
* Update targetSdk to Android Pie (API level 28)
|
||||
### Added
|
||||
|
||||
### 1.7.8 (April 21, 2018)
|
||||
- Add support for Android 14 (@iSoron, @hiqua)
|
||||
- Allow user to change app language (@leondzn)
|
||||
|
||||
* Add support for adaptive icons (Oreo)
|
||||
* Add support for notification channels (Oreo)
|
||||
* Update translations
|
||||
### Fixed
|
||||
|
||||
### 1.7.7 (September 30, 2017)
|
||||
- Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872)
|
||||
- Fix splash screen background color in dark mode (@SIKV, #1888)
|
||||
|
||||
* Fix bug that caused reminders to show repeatedly on DST changes
|
||||
## [2.1.3] -- 2023-08-28
|
||||
|
||||
### 1.7.6 (July 18, 2017)
|
||||
### Fixed
|
||||
|
||||
* Fix bug that caused widgets not to render sometimes
|
||||
* Fix other minor bugs
|
||||
* Update translations
|
||||
- Use text input on Samsung devices (@iSoron, #1719)
|
||||
- Prevent crash if alarm permission is revoked (@iSoron)
|
||||
- Adjust widget colors (@iSoron)
|
||||
- Fix bug preventing screens from updating at midnight (@iSoron)
|
||||
- Fix skip button in locales that use comma instead of dot (@iSoron, #1721)
|
||||
|
||||
### 1.7.3 (May 30, 2017)
|
||||
## [2.1.2] -- 2023-05-26
|
||||
|
||||
* Improve performance of 'sort by score'
|
||||
* Other minor bug fixes
|
||||
### Fixed
|
||||
|
||||
### 1.7.2 (May 27, 2017)
|
||||
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
|
||||
- Fix widget corners on Android 12 (@iSoron)
|
||||
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
|
||||
- Prevent soft keyboard from covering entry popup (@iSoron)
|
||||
- Accept comma (instead of dot) in certain locales (@iSoron)
|
||||
|
||||
* Fix crash at startup
|
||||
### Changed
|
||||
|
||||
### 1.7.1 (May 21, 2017)
|
||||
- Remove update delay after entering a checkmark (@iSoron)
|
||||
|
||||
* Fix crash (BadParcelableException)
|
||||
* Fix layout for RTL languages such as Arabic
|
||||
* Automatically detect and reject invalid database files
|
||||
* Add Hebrew translation
|
||||
### Removed
|
||||
|
||||
### 1.7.0 (Mar 31, 2017)
|
||||
- Remove stack widgets (@iSoron)
|
||||
|
||||
* 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
|
||||
## [2.1.1] -- 2022-09-24
|
||||
|
||||
### 1.6.2 (Oct 13, 2016)
|
||||
### Fixed
|
||||
|
||||
* Fix crash on Android 4.1
|
||||
- Fix Tasker plugin (@iSoron, #1503)
|
||||
|
||||
### 1.6.1 (Oct 10, 2016)
|
||||
## [2.1.0] -- 2022-09-10
|
||||
|
||||
* Fix a crash at startup when database is corrupted
|
||||
### Added
|
||||
|
||||
### 1.6.0 (Oct 10, 2016)
|
||||
- Allow user to add notes to specific dates (@vbh, #1103)
|
||||
- Allow user to track "at most" numerical habits (@KristianTashkov, #1101)
|
||||
- Allow user to add skips to measurable habits (@kalina559, #1319)
|
||||
- Bring back custom frequencies (x times in y days) (@hiqua, #1079)
|
||||
- Improve number picker (@hiqua, @iSoron, #1082, #1370)
|
||||
- Add new checkmark and number picker (@iSoron, #1370)
|
||||
- Allow user to import numerical habits from HabitBull (@hiqua, #1278)
|
||||
- Add support for Android 13 themed icons (@cheeeeer, #1497)
|
||||
|
||||
* 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
|
||||
### Removed
|
||||
|
||||
### 1.5.6 (Jun 19, 2016)
|
||||
- Hide snooze button Android 12 notifications (@hiqua, #1226)
|
||||
- Remove preference to set LED lights (@iSoron)
|
||||
|
||||
* Fix bug that prevented checkmark widget from working
|
||||
### Changed
|
||||
|
||||
### 1.5.5 (Jun 19, 2016)
|
||||
- Hide failed habits along with completed ones (@hiqua, #1052)
|
||||
- Cycle through all checkmark states when toggling (@iSoron)
|
||||
- Add delay after toggling a habit (@hiqua, @kalina559, #1147)
|
||||
- Small theme improvements (@KristianTashkov, #1113)
|
||||
- Left-align habit notes (@iSoron)
|
||||
- Increase target SDK to 31 (@hiqua)
|
||||
|
||||
* 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
|
||||
### Fixed
|
||||
|
||||
### 1.5.4 (May 29, 2016)
|
||||
- Fix small dialog buttons (@kalina559, #1096)
|
||||
- Fix invalid CSV files (@hiqua, #1177)
|
||||
- Fix small issues in calendar chart (@kalina559, #1314)
|
||||
- Resort habit list after edit (@hiqua, #1350)
|
||||
- Fix marker scaling in frequency display (@eduebernal, #1425)
|
||||
- Fix widgets not working correctly on API 33 (@iSoron, #1488)
|
||||
|
||||
* Fix crash upon opening settings screen in some phones
|
||||
* Fix missing folders in CSV archive
|
||||
* Add Serbian translation
|
||||
### Refactoring & Testing
|
||||
|
||||
### 1.5.3 (May 22, 2016)
|
||||
- Replace raster icons by vector assets (@kalina559)
|
||||
- Remove JVM dependencies from uhabits-core module (@sgallese)
|
||||
- Add various missing tests (@sgallese)
|
||||
- Upgrade project dependencies (@hiqua, @sgallese)
|
||||
|
||||
* Complete Arabic and Czech translations
|
||||
* Fix crash at startup
|
||||
* Fix checkmark widget on custom launchers
|
||||
## [2.0.3] - 2021-08-21
|
||||
|
||||
### 1.5.2 (May 19, 2016)
|
||||
### Fixed
|
||||
|
||||
* Fix missing attachment on bug reports
|
||||
* Fix bug that prevents some widgets from rendering
|
||||
* Complete Japanese translation
|
||||
- Improve automatic checkmarks for monthly habits (@iSoron, #947)
|
||||
- Fix small theme issues (@iSoron)
|
||||
- Fix ANR on some Samsung phones (@iSoron, #962)
|
||||
- Fix dates before the year 2000 (@iSoron, #967)
|
||||
- Fix notification adding checkmarks to the wrong day (@hiqua, #969)
|
||||
- Fix crashes in widgets (@hiqua, @iSoron, #907, #966, #965)
|
||||
- Fix crash when moving habits (@hiqua, #968)
|
||||
|
||||
### 1.5.1 (May 17, 2016)
|
||||
## [2.0.2] - 2021-05-23
|
||||
|
||||
* Fix build on F-Droid
|
||||
### Changed
|
||||
|
||||
### 1.5.0 (May 15, 2016)
|
||||
- Make checkmark widget resizable
|
||||
|
||||
* 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
|
||||
### Fixed
|
||||
|
||||
### 1.4.1 (April 9, 2016)
|
||||
- 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 error message on widgets, instead of crashing
|
||||
* Complete French translation
|
||||
* Minor fixes to other translations
|
||||
## [2.0.1] - 2021-05-09
|
||||
|
||||
### 1.4.0 (April 7, 2016)
|
||||
### Added
|
||||
|
||||
* 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
|
||||
- Make midnight delay optional and disabled by default (@hiqua)
|
||||
- Add arrows to sort menu (@iSoron)
|
||||
|
||||
### 1.3.3 (March 20, 2016)
|
||||
### Removed
|
||||
|
||||
* Add Spanish and Korean translations
|
||||
* Make small corrections to other translations
|
||||
* Fix incorrect date in history calendar
|
||||
- Temporarily remove experimental device sync functionality. This feature will be re-added in
|
||||
Loop 2.1.
|
||||
|
||||
### 1.3.2 (March 18, 2016)
|
||||
### Changed
|
||||
|
||||
* Add Arabic, Italian, Polish, Russian and Swedish translations
|
||||
* Minor fixes to German and French translations
|
||||
* Minor bug fixes
|
||||
- Make implicit checkmarks easier to read (@iSoron)
|
||||
- Update and improve list of translators (@hiqua, @iSoron)
|
||||
|
||||
### 1.3.1 (March 15, 2016)
|
||||
### Fixed
|
||||
|
||||
* 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
|
||||
- Disable transparency for stacked widgets (@hiqua)
|
||||
- Fix various color issues on the dark theme (@hiqua, @iSoron)
|
||||
- Fix "customize notifications" on older devices (@hiqua)
|
||||
- Fix snooze button in notifications when device is locked (@hiqua)
|
||||
- Fix a crash when deleting habits (@engineering4good)
|
||||
- Fix checkmark widget not rendering properly on some Samsung phones (@iSoron)
|
||||
|
||||
### 1.3.0 (March 12, 2016)
|
||||
### Refactoring & Testing
|
||||
|
||||
* 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
|
||||
- Finish conversion of the entire project to Kotlin (@hiqua, @iSoron, @MarKco)
|
||||
- Automatically run large tests on GitHub Actions (@iSoron)
|
||||
- Remove unused v21 resources (@hiqua)
|
||||
|
||||
### 1.2.0 (March 4, 2016)
|
||||
## [2.0.0-alpha] - 2020-11-29
|
||||
|
||||
* 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
|
||||
### Added
|
||||
|
||||
### 1.1.1 (February 24, 2016)
|
||||
- Track numeric habits (@iSoron, @namnl)
|
||||
- Skip days without breaking streak (@KristianTashkov)
|
||||
- Sort habits by status (@hiqua)
|
||||
- Sort habits in reverse order (@iSoron)
|
||||
- Add notes to habits (@recheej)
|
||||
- Improve readibility of charts (@chennemann)
|
||||
- Delay new day until 3am (@KristianTashkov)
|
||||
- Export backups daily (@iSoron)
|
||||
|
||||
* Show 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
|
||||
### Removed
|
||||
|
||||
### 1.0.0 (February 19, 2016)
|
||||
- Drop support to devices older than Android 6.0 (API 23)
|
||||
|
||||
* Initial release
|
||||
### 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
|
||||
|
||||
24
NOTICE.md
@@ -1,6 +1,6 @@
|
||||
# Copyright Notices
|
||||
|
||||
### ActiveAndroid
|
||||
## ActiveAndroid
|
||||
|
||||
<https://github.com/pardom/ActiveAndroid>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Android Open Source Project
|
||||
## Android Open Source Project
|
||||
|
||||
<https://source.android.com/>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### FontAwesome
|
||||
## FontAwesome
|
||||
|
||||
<http://fontawesome.io>
|
||||
|
||||
@@ -59,7 +59,7 @@ under the SIL OFL 1.1.
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
### Material Design Icons
|
||||
## Material Design Icons
|
||||
|
||||
<https://github.com/google/material-design-icons>
|
||||
|
||||
@@ -67,7 +67,7 @@ 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
|
||||
## Android Flow Layout
|
||||
|
||||
<https://github.com/ApmeM/android-flowlayout>
|
||||
|
||||
@@ -87,7 +87,7 @@ Extended linear layout that wrap its content when there is no place in the curre
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
### Dagger 2
|
||||
## Dagger 2
|
||||
|
||||
<https://github.com/google/dagger>
|
||||
|
||||
@@ -108,7 +108,7 @@ A fast dependency injector for Android and Java.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### AutoFactory
|
||||
## AutoFactory
|
||||
|
||||
<https://github.com/google/auto/tree/master/factory>
|
||||
|
||||
@@ -128,7 +128,7 @@ A source code generator for JSR-330-compatible factories.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### Retrolambda
|
||||
## Retrolambda
|
||||
|
||||
<https://github.com/orfjackal/retrolambda>
|
||||
|
||||
@@ -138,7 +138,7 @@ Backport of Java 8's lambda expressions to Java 7, 6 and 5
|
||||
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
|
||||
## PebbleKit SDK
|
||||
|
||||
<https://github.com/pebble/pebble-android-sdk/>
|
||||
|
||||
@@ -147,7 +147,7 @@ Android PebbleKit SDK to talk to the Pebble via Bluetooth
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2014 - 2015 Pebble Technology
|
||||
|
||||
### AppIntro
|
||||
## AppIntro
|
||||
|
||||
<https://github.com/PaoloRotolo/AppIntro>
|
||||
|
||||
@@ -168,7 +168,7 @@ Make a cool intro for your Android app.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### ButterKnife
|
||||
## ButterKnife
|
||||
|
||||
<https://github.com/JakeWharton/butterknife>
|
||||
|
||||
@@ -188,7 +188,7 @@ Bind Android views and callbacks to fields and methods
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
### opencsv
|
||||
## opencsv
|
||||
|
||||
<http://opencsv.sourceforge.net/>
|
||||
|
||||
|
||||
96
README.md
@@ -1,11 +1,15 @@
|
||||
<a href="https://github.com/iSoron/uhabits/actions?query=workflow%3A%22Build+%26+Test%22">
|
||||
<img src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/iSoron/uhabits/releases">
|
||||
<img src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
||||
</a>
|
||||
|
||||
# Loop Habit Tracker
|
||||
<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 alt="Build & Test" src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/iSoron/uhabits/releases/latest">
|
||||
<img alt="release" src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
||||
</a>
|
||||
<a href="https://github.com/iSoron/uhabits/discussions">
|
||||
<img alt="GitHub" src="https://img.shields.io/badge/GitHub-Discussions-%23fc4ebc" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
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
|
||||
@@ -13,8 +17,8 @@ show you how your habits improved over time. It is completely ad-free and open
|
||||
source.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" height="75px"/></a>
|
||||
<a href="http://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="http://i.imgur.com/baSPE7X.png" height="75px"/></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80px"/></a>
|
||||
<a href="https://f-droid.org/app/org.isoron.uhabits"><img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80px"/></a>
|
||||
</p>
|
||||
|
||||
## Screenshots
|
||||
@@ -28,32 +32,23 @@ source.
|
||||
|
||||
## 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.
|
||||
* **Beautiful, minimalistic and lightweight interface.** 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.
|
||||
* **Habit score.** 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.
|
||||
* **Flexible schedules.** 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.
|
||||
* **Reminders.** 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.
|
||||
* **Widgets.** 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.
|
||||
* **Take control of your data.** 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.
|
||||
* **No limitations.** 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).
|
||||
* **Completely ad-free and open source.** 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.
|
||||
* **Works offline and respects your privacy.** 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
|
||||
|
||||
@@ -70,8 +65,10 @@ contribute, even if you are not a software developer.
|
||||
|
||||
* **Report bugs, suggest features.** The easiest way to contribute is to simply
|
||||
use the app and let us know if you find any problems or have any suggestions
|
||||
to improve it. You can either use the link inside the app, or open an issue
|
||||
at GitHub. If you would like to receive the newest versions of the app
|
||||
to improve it. To report a problem, please [create a new bug report](https://github.com/iSoron/uhabits/issues/new/choose).
|
||||
To request a new feature or vote on existing feature requests, please visit
|
||||
our [GitHub Discussions page](https://github.com/iSoron/uhabits/discussions/categories/feature-requests).
|
||||
If you would like to receive the newest versions of the app
|
||||
earlier than everyone else, [join our open beta on Google Play][beta].
|
||||
|
||||
* **Spread the word.** If you like the app, share it with your family, friends
|
||||
@@ -84,13 +81,13 @@ contribute, even if you are not a software developer.
|
||||
is already completed, you are also very welcome to join and proofread it.
|
||||
|
||||
* **Write some code.** If you are an Android developer, you are very welcome to
|
||||
contribute with code. Please, see the [developer guidelines][dev-guide] for more details.
|
||||
contribute with code. Please see the [guidelines](https://github.com/iSoron/uhabits/blob/dev/docs/GUIDELINES.md).
|
||||
|
||||
## License
|
||||
|
||||
<img align="right" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||
<img align="right" alt="GPL v3" src="https://www.gnu.org/graphics/gplv3-88x31.png">
|
||||
|
||||
Copyright (C) 2016-2019 Álinson Santos Xavier <isoron@gmail.com>
|
||||
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
|
||||
@@ -103,24 +100,23 @@ contribute, even if you are not a software developer.
|
||||
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/>.
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[screen1]: screenshots/uhabits1.png
|
||||
[screen2]: screenshots/uhabits2.png
|
||||
[screen3]: screenshots/uhabits3.png
|
||||
[screen4]: screenshots/uhabits4.png
|
||||
[screen5]: screenshots/uhabits5.png
|
||||
[screen6]: screenshots/uhabits6.png
|
||||
[screen1th]: screenshots/uhabits1_th.png
|
||||
[screen2th]: screenshots/uhabits2_th.png
|
||||
[screen3th]: screenshots/uhabits3_th.png
|
||||
[screen4th]: screenshots/uhabits4_th.png
|
||||
[screen5th]: screenshots/uhabits5_th.png
|
||||
[screen6th]: screenshots/uhabits6_th.png
|
||||
[poedit]: http://translate.loophabits.org
|
||||
[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]: https://translate.loophabits.org
|
||||
[playstore]: https://play.google.com/store/apps/details?id=org.isoron.uhabits
|
||||
[releases]: https://github.com/iSoron/uhabits/releases
|
||||
[fdroid]: http://f-droid.org/app/org.isoron.uhabits
|
||||
[dev-guide]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines
|
||||
[build]: https://github.com/iSoron/uhabits/wiki/Developer-guidelines#building
|
||||
[fdroid]: https://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
android/android-base/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,33 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion MIN_SDK_VERSION as Integer
|
||||
targetSdkVersion TARGET_SDK_VERSION as Integer
|
||||
buildConfigField 'int', 'VERSION_CODE', "$VERSION_CODE"
|
||||
buildConfigField 'String', 'VERSION_NAME', "\"$VERSION_NAME\""
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
implementation "org.apache.commons:commons-lang3:3.5"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||
}
|
||||
25
android/android-base/proguard-rules.pro
vendored
@@ -1,25 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,2 +0,0 @@
|
||||
<manifest package="org.isoron.androidbase"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"/>
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase
|
||||
|
||||
import android.content.Context
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
|
||||
class SSLContextProvider @Inject constructor(@param:AppContext private val context: Context) {
|
||||
fun getCACertSSLContext(): SSLContext {
|
||||
try {
|
||||
val cf = CertificateFactory.getInstance("X.509")
|
||||
val ca = cf.generateCertificate(context.assets.open("cacert.pem"))
|
||||
val ks = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
|
||||
load(null, null)
|
||||
setCertificateEntry("ca", ca)
|
||||
}
|
||||
val alg = TrustManagerFactory.getDefaultAlgorithm()
|
||||
val tmf = TrustManagerFactory.getInstance(alg).apply {
|
||||
init(ks)
|
||||
}
|
||||
return SSLContext.getInstance("TLS").apply {
|
||||
init(null, tmf.trustManagers, null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase.activities
|
||||
|
||||
import android.R.anim
|
||||
import android.content.*
|
||||
import android.os.*
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.*
|
||||
import org.isoron.androidbase.*
|
||||
|
||||
/**
|
||||
* Base class for all activities in the application.
|
||||
*
|
||||
* This class delegates the responsibilities of an Android activity to other classes. For example,
|
||||
* callbacks related to menus are forwarded to a []BaseMenu], while callbacks related to activity
|
||||
* results are forwarded to a [BaseScreen].
|
||||
*
|
||||
*
|
||||
* A BaseActivity also installs an [java.lang.Thread.UncaughtExceptionHandler] to the main thread.
|
||||
* By default, this handler is an instance of BaseExceptionHandler, which logs the exception to the
|
||||
* disk before the application crashes. To the default handler, you should override the method
|
||||
* getExceptionHandler.
|
||||
*/
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
private var baseMenu: BaseMenu? = null
|
||||
private var screen: BaseScreen? = null
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
if (menu != null) baseMenu?.onCreate(menuInflater, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
if (item == null) return false
|
||||
return baseMenu?.onItemSelected(item) ?: false
|
||||
}
|
||||
|
||||
fun restartWithFade(cls: Class<*>?) {
|
||||
Handler().postDelayed({
|
||||
finish()
|
||||
overridePendingTransition(anim.fade_in, anim.fade_out)
|
||||
startActivity(Intent(this, cls))
|
||||
}, 500) // HACK: Let the menu disappear first
|
||||
}
|
||||
|
||||
fun setBaseMenu(baseMenu: BaseMenu?) {
|
||||
this.baseMenu = baseMenu
|
||||
}
|
||||
|
||||
fun setScreen(screen: BaseScreen?) {
|
||||
this.screen = screen
|
||||
}
|
||||
|
||||
fun showDialog(dialog: AppCompatDialogFragment, tag: String?) {
|
||||
dialog.show(supportFragmentManager, tag)
|
||||
}
|
||||
|
||||
fun showDialog(dialog: AppCompatDialog) {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
override fun onActivityResult(request: Int, result: Int, data: Intent?) {
|
||||
val screen = screen
|
||||
if(screen == null) super.onActivityResult(request, result, data)
|
||||
else screen.onResult(request, result, data)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Thread.setDefaultUncaughtExceptionHandler(getExceptionHandler())
|
||||
}
|
||||
|
||||
private fun getExceptionHandler() = BaseExceptionHandler(this)
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
screen?.reattachDialogs()
|
||||
}
|
||||
|
||||
override fun startActivity(intent: Intent?) {
|
||||
try {
|
||||
super.startActivity(intent)
|
||||
} catch(e: ActivityNotFoundException) {
|
||||
this.screen?.showMessage(R.string.activity_not_found)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase.activities
|
||||
|
||||
import android.view.*
|
||||
import androidx.annotation.*
|
||||
|
||||
/**
|
||||
* Base class for all the menus in the application.
|
||||
*
|
||||
* This class receives from BaseActivity all callbacks related to menus, such as
|
||||
* menu creation and click events. It also handles some implementation details
|
||||
* of creating menus in Android, such as inflating the resources.
|
||||
*/
|
||||
abstract class BaseMenu(private val activity: BaseActivity) {
|
||||
|
||||
/**
|
||||
* Declare that the menu has changed, and should be recreated.
|
||||
*/
|
||||
fun invalidate() {
|
||||
activity.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* The given menu is already inflated and ready to receive items. The
|
||||
* application should override this method and add items to the menu here.
|
||||
*
|
||||
* @param menu the menu that is being created.
|
||||
*/
|
||||
open fun onCreate(menu: Menu) {}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* This method should not be overridden. The application should override
|
||||
* the methods onCreate(Menu) and getMenuResourceId instead.
|
||||
*
|
||||
* @param inflater a menu inflater, for creating the menu
|
||||
* @param menu the menu that is being created.
|
||||
*/
|
||||
fun onCreate(inflater: MenuInflater, menu: Menu) {
|
||||
menu.clear()
|
||||
inflater.inflate(getMenuResourceId(), menu)
|
||||
onCreate(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever an item on the menu is selected.
|
||||
*
|
||||
* @param item the item that was selected.
|
||||
* @return true if the event was consumed, or false otherwise
|
||||
*/
|
||||
open fun onItemSelected(item: MenuItem): Boolean = false
|
||||
|
||||
/**
|
||||
* Returns the id of the resource that should be used to inflate this menu.
|
||||
*
|
||||
* @return id of the menu resource.
|
||||
*/
|
||||
@MenuRes
|
||||
protected abstract fun getMenuResourceId(): Int
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import org.isoron.androidbase.R
|
||||
import org.isoron.androidbase.utils.InterfaceUtils.dpToPixels
|
||||
import org.isoron.androidbase.utils.StyledResources
|
||||
|
||||
/**
|
||||
* Base class for all root views in the application.
|
||||
*
|
||||
*
|
||||
* A root view is an Android view that is directly attached to an activity. This
|
||||
* view usually includes a toolbar and a progress bar. This abstract class hides
|
||||
* some of the complexity of setting these things up, for every version of
|
||||
* Android.
|
||||
*/
|
||||
abstract class BaseRootView(context: Context) : FrameLayout(context) {
|
||||
var displayHomeAsUp = false
|
||||
var screen: BaseScreen? = null
|
||||
private set
|
||||
|
||||
open fun getToolbar(): Toolbar {
|
||||
return findViewById(R.id.toolbar)
|
||||
?: throw RuntimeException("Your BaseRootView should have a toolbar with id R.id.toolbar")
|
||||
}
|
||||
|
||||
open fun getToolbarColor(): Int = StyledResources(context).getColor(R.attr.colorPrimary)
|
||||
|
||||
protected open fun initToolbar() {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
getToolbar().elevation = dpToPixels(context, 2f)
|
||||
findViewById<View>(R.id.toolbarShadow)?.visibility = View.GONE
|
||||
findViewById<View>(R.id.headerShadow)?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun onAttachedToScreen(screen: BaseScreen?) {
|
||||
this.screen = screen
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase.activities
|
||||
|
||||
import android.content.*
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.*
|
||||
import android.view.*
|
||||
import android.widget.*
|
||||
import androidx.annotation.*
|
||||
import androidx.appcompat.app.*
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.*
|
||||
import com.google.android.material.snackbar.*
|
||||
import org.isoron.androidbase.*
|
||||
import org.isoron.androidbase.utils.*
|
||||
import org.isoron.androidbase.utils.ColorUtils.mixColors
|
||||
import org.isoron.androidbase.utils.InterfaceUtils.dpToPixels
|
||||
import java.io.*
|
||||
|
||||
/**
|
||||
* Base class for all screens in the application.
|
||||
*
|
||||
* Screens are responsible for deciding what root views and what menus should be attached to the
|
||||
* main window. They are also responsible for showing other screens and for receiving their results.
|
||||
*/
|
||||
open class BaseScreen(@JvmField protected var activity: BaseActivity) {
|
||||
|
||||
private var rootView: BaseRootView? = null
|
||||
private var selectionMenu: BaseSelectionMenu? = null
|
||||
private var snackbar: Snackbar? = null
|
||||
|
||||
/**
|
||||
* Notifies the screen that its contents should be updated.
|
||||
*/
|
||||
fun invalidate() {
|
||||
rootView?.invalidate()
|
||||
}
|
||||
|
||||
fun invalidateToolbar() {
|
||||
rootView?.let { root ->
|
||||
activity.runOnUiThread {
|
||||
val toolbar = root.getToolbar()
|
||||
activity.setSupportActionBar(toolbar)
|
||||
activity.supportActionBar?.let { actionBar ->
|
||||
actionBar.setDisplayHomeAsUpEnabled(root.displayHomeAsUp)
|
||||
val color = root.getToolbarColor()
|
||||
setActionBarColor(actionBar, color)
|
||||
setStatusBarColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when another Activity has finished, and has returned some result.
|
||||
*
|
||||
* @param requestCode the request code originally supplied to startActivityForResult.
|
||||
* @param resultCode the result code sent by the other activity.
|
||||
* @param data an Intent containing extra data sent by the other
|
||||
* activity.
|
||||
* @see {@link android.app.Activity.onActivityResult
|
||||
*/
|
||||
open fun onResult(requestCode: Int, resultCode: Int, data: Intent?) {}
|
||||
|
||||
/**
|
||||
* Called after activity has been recreated, and the dialogs should be
|
||||
* reattached to their controllers.
|
||||
*/
|
||||
open fun reattachDialogs() {}
|
||||
|
||||
/**
|
||||
* Sets the menu to be shown by this screen.
|
||||
*
|
||||
*
|
||||
* This menu will be visible if when there is no active selection operation.
|
||||
* If the provided menu is null, then no menu will be shown.
|
||||
*
|
||||
* @param menu the menu to be shown.
|
||||
*/
|
||||
fun setMenu(menu: BaseMenu?) {
|
||||
activity.setBaseMenu(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root view for this screen.
|
||||
*
|
||||
* @param rootView the root view for this screen.
|
||||
*/
|
||||
fun setRootView(rootView: BaseRootView?) {
|
||||
this.rootView = rootView
|
||||
activity.setContentView(rootView)
|
||||
rootView?.let {
|
||||
it.onAttachedToScreen(this)
|
||||
invalidateToolbar()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the menu to be shown when a selection is active on the screen.
|
||||
*
|
||||
* @param menu the menu to be shown during a selection
|
||||
*/
|
||||
fun setSelectionMenu(menu: BaseSelectionMenu?) {
|
||||
selectionMenu = menu
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a message on the screen.
|
||||
*
|
||||
* @param stringId the string resource id for this message.
|
||||
*/
|
||||
fun showMessage(@StringRes stringId: Int?, rootView: View?) {
|
||||
var snackbar = this.snackbar
|
||||
if (stringId == null || rootView == null) return
|
||||
if (snackbar == null) {
|
||||
snackbar = Snackbar.make(rootView, stringId, Snackbar.LENGTH_SHORT)
|
||||
val tvId = R.id.snackbar_text
|
||||
val tv = snackbar.view.findViewById<TextView>(tvId)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
this.snackbar = snackbar
|
||||
}
|
||||
snackbar.setText(stringId)
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
fun showMessage(@StringRes stringId: Int?) {
|
||||
showMessage(stringId, this.rootView)
|
||||
}
|
||||
|
||||
fun showSendEmailScreen(@StringRes toId: Int, @StringRes subjectId: Int, content: String?) {
|
||||
val to = activity.getString(toId)
|
||||
val subject = activity.getString(subjectId)
|
||||
activity.startActivity(Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "message/rfc822"
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(to))
|
||||
putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
putExtra(Intent.EXTRA_TEXT, content)
|
||||
})
|
||||
}
|
||||
|
||||
fun showSendFileScreen(archiveFilename: String) {
|
||||
val file = File(archiveFilename)
|
||||
val fileUri = FileProvider.getUriForFile(activity, "org.isoron.uhabits", file)
|
||||
activity.startActivity(Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "application/zip"
|
||||
putExtra(Intent.EXTRA_STREAM, fileUri)
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the screen to start a selection.
|
||||
*
|
||||
* If a selection menu was provided, this menu will be shown instead of the regular one.
|
||||
*/
|
||||
fun startSelection() {
|
||||
activity.startSupportActionMode(ActionModeWrapper())
|
||||
}
|
||||
|
||||
private fun setActionBarColor(actionBar: ActionBar, color: Int) {
|
||||
val drawable = ColorDrawable(color)
|
||||
actionBar.setBackgroundDrawable(drawable)
|
||||
}
|
||||
|
||||
private fun setStatusBarColor(baseColor: Int) {
|
||||
val darkerColor = mixColors(baseColor, Color.BLACK, 0.75f)
|
||||
activity.window.statusBarColor = darkerColor
|
||||
}
|
||||
|
||||
private inner class ActionModeWrapper : ActionMode.Callback {
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
val selectionMenu = selectionMenu
|
||||
if (item == null || selectionMenu == null) return false
|
||||
return selectionMenu.onItemClicked(item)
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
if (mode == null || menu == null) return false
|
||||
val selectionMenu = selectionMenu ?: return false
|
||||
selectionMenu.onCreate(activity.menuInflater, mode, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
selectionMenu?.onFinish()
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
val selectionMenu = selectionMenu
|
||||
if (selectionMenu == null || menu == null) return false
|
||||
return selectionMenu.onPrepare(menu)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Deprecated("")
|
||||
fun getDefaultActionBarColor(context: Context) =
|
||||
StyledResources(context).getColor(R.attr.colorPrimary)
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("")
|
||||
fun setupActionBarColor(activity: AppCompatActivity, color: Int) {
|
||||
val toolbar = activity.findViewById<Toolbar>(R.id.toolbar) ?: return
|
||||
activity.setSupportActionBar(toolbar)
|
||||
val supportActionBar = activity.supportActionBar ?: return
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true)
|
||||
val drawable = ColorDrawable(color)
|
||||
supportActionBar.setBackgroundDrawable(drawable)
|
||||
val darkerColor = mixColors(color, Color.BLACK, 0.75f)
|
||||
activity.window.statusBarColor = darkerColor
|
||||
toolbar.elevation = dpToPixels(activity, 2f)
|
||||
activity.findViewById<View>(R.id.toolbarShadow)?.visibility = View.GONE
|
||||
activity.findViewById<View>(R.id.headerShadow)?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.androidbase.activities
|
||||
|
||||
import android.view.*
|
||||
import androidx.appcompat.view.ActionMode
|
||||
|
||||
/**
|
||||
* Base class for all the selection menus in the application.
|
||||
*
|
||||
* A selection menu is a menu that appears when the screen starts a selection
|
||||
* operation. It contains actions that modify the selected items, such as delete
|
||||
* or archive. Since it replaces the toolbar, it also has a title.
|
||||
*
|
||||
* This class hides many implementation details of creating such menus in
|
||||
* Android. The interface is supposed to look very similar to [BaseMenu],
|
||||
* with a few additional methods, such as finishing the selection operation.
|
||||
* Internally, it uses an [ActionMode].
|
||||
*/
|
||||
abstract class BaseSelectionMenu {
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
/**
|
||||
* Finishes the selection operation.
|
||||
*/
|
||||
fun finish() {
|
||||
actionMode?.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare that the menu has changed, and should be recreated.
|
||||
*/
|
||||
fun invalidate() {
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the menu is first displayed.
|
||||
*
|
||||
* This method should not be overridden. The application should override
|
||||
* the methods onCreate(Menu) and getMenuResourceId instead.
|
||||
*
|
||||
* @param inflater a menu inflater, for creating the menu
|
||||
* @param mode the action mode associated with this menu.
|
||||
* @param menu the menu that is being created.
|
||||
*/
|
||||
fun onCreate(inflater: MenuInflater, mode: ActionMode, menu: Menu) {
|
||||
actionMode = mode
|
||||
inflater.inflate(getResourceId(), menu)
|
||||
onCreate(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the selection operation is about to finish.
|
||||
*/
|
||||
open fun onFinish() {}
|
||||
|
||||
/**
|
||||
* Called whenever an item on the menu is selected.
|
||||
*
|
||||
* @param item the item that was selected.
|
||||
* @return true if the event was consumed, or false otherwise
|
||||
*/
|
||||
open fun onItemClicked(item: MenuItem): Boolean = false
|
||||
|
||||
/**
|
||||
* Called whenever the menu is invalidated.
|
||||
*
|
||||
* @param menu the menu to be refreshed
|
||||
* @return true if the menu has changes, false otherwise
|
||||
*/
|
||||
open fun onPrepare(menu: Menu): Boolean = false
|
||||
|
||||
/**
|
||||
* Sets the title of the selection menu.
|
||||
*
|
||||
* @param title the new title.
|
||||
*/
|
||||
fun setTitle(title: String?) {
|
||||
actionMode?.title = title
|
||||
}
|
||||
|
||||
protected abstract fun getResourceId(): Int
|
||||
|
||||
/**
|
||||
* Called when the menu is first created.
|
||||
*
|
||||
* @param menu the menu being created
|
||||
*/
|
||||
protected fun onCreate(menu: Menu) {}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<resources>
|
||||
<item name="toolbar" type="id" />
|
||||
<item name="toolbarShadow" type="id" />
|
||||
<item name="headerShadow" type="id" />
|
||||
<attr name="palette" format="reference"/>
|
||||
</resources>
|
||||
1
android/android-pickers/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,24 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION as Integer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion MIN_SDK_VERSION as Integer
|
||||
targetSdkVersion TARGET_SDK_VERSION as Integer
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
}
|
||||
25
android/android-pickers/proguard-rules.pro
vendored
@@ -1,25 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /gemini-b/opt/android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,2 +0,0 @@
|
||||
<manifest package="com.android"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"/>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="select_hours"/>
|
||||
<string name="select_minutes"/>
|
||||
<string name="color_picker_default_title"/>
|
||||
<string name="clear"/>
|
||||
<string name="clear_label"/>
|
||||
<string name="done_label"/>
|
||||
</resources>
|
||||
@@ -1,21 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$BUILD_TOOLS_VERSION"
|
||||
classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
|
||||
classpath "org.ajoberstar:grgit:1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
283
android/build.sh
@@ -1,283 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
# This file is part of Loop Habit Tracker.
|
||||
#
|
||||
# Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
ADB="${ANDROID_HOME}/platform-tools/adb"
|
||||
EMULATOR="${ANDROID_HOME}/tools/emulator"
|
||||
GRADLE="./gradlew --stacktrace"
|
||||
PACKAGE_NAME=org.isoron.uhabits
|
||||
OUTPUTS_DIR=uhabits-android/build/outputs
|
||||
VERSION=$(cat gradle.properties | grep VERSION_NAME | sed -e 's/.*=//g;s/ //g')
|
||||
|
||||
if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
|
||||
echo "Error: ANDROID_HOME is not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_error() {
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;31m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info() {
|
||||
if [ ! -z "$TEAMCITY_VERSION" ]; then
|
||||
echo "###teamcity[progressMessage '$1']"
|
||||
else
|
||||
local COLOR='\033[1;32m'
|
||||
local NC='\033[0m'
|
||||
echo -e "$COLOR>>> $1 $NC"
|
||||
fi
|
||||
}
|
||||
|
||||
fail() {
|
||||
log_error "BUILD FAILED"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ ! -z $RELEASE ]; then
|
||||
log_info "Reading secret env variables from ../.secret/env"
|
||||
source ../.secret/env || fail
|
||||
fi
|
||||
|
||||
|
||||
run_adb_as_root() {
|
||||
log_info "Running adb as root"
|
||||
$ADB root
|
||||
}
|
||||
|
||||
build_apk() {
|
||||
log_info "Removing old APKs..."
|
||||
rm -vf build/*.apk
|
||||
|
||||
if [ ! -z $RELEASE ]; then
|
||||
log_info "Building release APK"
|
||||
$GRADLE assembleRelease
|
||||
cp -v uhabits-android/build/outputs/apk/release/uhabits-android-release.apk build/loop-$VERSION-release.apk
|
||||
fi
|
||||
|
||||
log_info "Building debug APK"
|
||||
$GRADLE assembleDebug --stacktrace || fail
|
||||
cp -v uhabits-android/build/outputs/apk/debug/uhabits-android-debug.apk build/loop-$VERSION-debug.apk
|
||||
}
|
||||
|
||||
build_instrumentation_apk() {
|
||||
log_info "Building instrumentation APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE assembleAndroidTest \
|
||||
-Pandroid.injected.signing.store.file=$LOOP_KEY_STORE \
|
||||
-Pandroid.injected.signing.store.password=$LOOP_STORE_PASSWORD \
|
||||
-Pandroid.injected.signing.key.alias=$LOOP_KEY_ALIAS \
|
||||
-Pandroid.injected.signing.key.password=$LOOP_KEY_PASSWORD || fail
|
||||
else
|
||||
$GRADLE assembleAndroidTest || fail
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall_apk() {
|
||||
log_info "Uninstalling existing APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}
|
||||
}
|
||||
|
||||
install_test_butler() {
|
||||
log_info "Installing Test Butler"
|
||||
$ADB uninstall com.linkedin.android.testbutler
|
||||
$ADB install tools/test-butler-app-2.0.2.apk
|
||||
}
|
||||
|
||||
install_apk() {
|
||||
log_info "Installing APK"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/release/uhabits-android-release.apk || fail
|
||||
else
|
||||
$ADB install -t -r ${OUTPUTS_DIR}/apk/debug/uhabits-android-debug.apk || fail
|
||||
fi
|
||||
}
|
||||
|
||||
install_test_apk() {
|
||||
log_info "Uninstalling existing test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
|
||||
log_info "Installing test APK"
|
||||
$ADB install -r ${OUTPUTS_DIR}/apk/androidTest/debug/uhabits-android-debug-androidTest.apk || fail
|
||||
}
|
||||
|
||||
run_instrumented_tests() {
|
||||
SIZE=$1
|
||||
log_info "Running instrumented tests"
|
||||
$ADB shell am instrument \
|
||||
-r -e coverage true -e size $SIZE \
|
||||
-w ${PACKAGE_NAME}.test/androidx.test.runner.AndroidJUnitRunner \
|
||||
| tee ${OUTPUTS_DIR}/instrument.txt
|
||||
|
||||
if grep "\(INSTRUMENTATION_STATUS_CODE.*-1\|FAILURES\)" $OUTPUTS_DIR/instrument.txt; then
|
||||
log_error "Some instrumented tests failed"
|
||||
fetch_images
|
||||
fetch_logcat
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#mkdir -p ${OUTPUTS_DIR}/code-coverage/connected/
|
||||
#$ADB pull /data/user/0/${PACKAGE_NAME}/files/coverage.ec \
|
||||
# ${OUTPUTS_DIR}/code-coverage/connected/ \
|
||||
# || log_error "COVERAGE REPORT NOT AVAILABLE"
|
||||
}
|
||||
|
||||
parse_instrumentation_results() {
|
||||
log_info "Parsing instrumented test results"
|
||||
java -jar tools/automator-log-converter-1.5.0.jar ${OUTPUTS_DIR}/instrument.txt || fail
|
||||
}
|
||||
|
||||
generate_coverage_badge() {
|
||||
log_info "Generating code coverage badge"
|
||||
CORE_REPORT=uhabits-core/build/reports/jacoco/test/jacocoTestReport.xml
|
||||
rm -f ${OUTPUTS_DIR}/coverage-badge.svg
|
||||
python3 tools/coverage-badge/badge.py -i $CORE_REPORT -o ${OUTPUTS_DIR}/coverage-badge
|
||||
}
|
||||
|
||||
fetch_logcat() {
|
||||
log_info "Fetching logcat"
|
||||
$ADB logcat -d > ${OUTPUTS_DIR}/logcat.txt
|
||||
}
|
||||
|
||||
run_jvm_tests() {
|
||||
log_info "Running JVM tests"
|
||||
if [ ! -z $RELEASE ]; then
|
||||
$GRADLE testReleaseUnitTest :uhabits-core:check || fail
|
||||
else
|
||||
$GRADLE testDebugUnitTest :uhabits-core:check || fail
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall_test_apk() {
|
||||
log_info "Uninstalling test APK"
|
||||
$ADB uninstall ${PACKAGE_NAME}.test
|
||||
}
|
||||
|
||||
fetch_images() {
|
||||
log_info "Fetching images"
|
||||
rm -rf $OUTPUTS_DIR/test-screenshots
|
||||
$ADB pull /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/ $OUTPUTS_DIR
|
||||
$ADB shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/files/test-screenshots/
|
||||
}
|
||||
|
||||
accept_images() {
|
||||
find $OUTPUTS_DIR/test-screenshots -name '*.expected*' -delete
|
||||
rsync -av $OUTPUTS_DIR/test-screenshots/ uhabits-android/src/androidTest/assets/
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
SIZE=$1
|
||||
run_adb_as_root
|
||||
install_test_butler
|
||||
uninstall_apk
|
||||
install_apk
|
||||
install_test_apk
|
||||
run_instrumented_tests $SIZE
|
||||
parse_instrumentation_results
|
||||
fetch_logcat
|
||||
uninstall_test_apk
|
||||
}
|
||||
|
||||
parse_opts() {
|
||||
OPTS=`getopt -o r --long release -n 'build.sh' -- "$@"`
|
||||
if [ $? != 0 ] ; then exit 1; fi
|
||||
eval set -- "$OPTS"
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
-r | --release ) RELEASE=1; shift ;;
|
||||
* ) break ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
remove_build_dir() {
|
||||
rm -rfv .gradle
|
||||
rm -rfv build
|
||||
rm -rfv android-base/build
|
||||
rm -rfv android-pickers/build
|
||||
rm -rfv uhabits-android/build
|
||||
rm -rfv uhabits-core/build
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
build)
|
||||
shift; parse_opts $*
|
||||
|
||||
build_apk
|
||||
build_instrumentation_apk
|
||||
run_jvm_tests
|
||||
#generate_coverage_badge
|
||||
;;
|
||||
|
||||
medium-tests)
|
||||
shift; parse_opts $*
|
||||
for attempt in {1..3}; do
|
||||
(run_tests medium) && exit 0
|
||||
done
|
||||
exit 1
|
||||
;;
|
||||
|
||||
large-tests)
|
||||
shift; parse_opts $*
|
||||
run_tests large
|
||||
;;
|
||||
|
||||
fetch-images)
|
||||
fetch_images
|
||||
;;
|
||||
|
||||
accept-images)
|
||||
accept_images
|
||||
;;
|
||||
|
||||
install)
|
||||
shift; parse_opts $*
|
||||
build_apk
|
||||
install_apk
|
||||
;;
|
||||
|
||||
clean)
|
||||
remove_build_dir
|
||||
;;
|
||||
|
||||
*)
|
||||
cat <<END
|
||||
Usage: $0 <command> [options]
|
||||
Builds, installs and tests Loop Habit Tracker
|
||||
|
||||
Commands:
|
||||
accept-images Copies fetched images to corresponding assets folder
|
||||
build Build APK and run JVM tests
|
||||
clean Remove build directory
|
||||
fetch-images Fetches failed view test images from device
|
||||
install Install app on connected device
|
||||
large-tests Run large-sized tests on connected device
|
||||
medium-tests Run medium-sized tests on connected device
|
||||
|
||||
Options:
|
||||
-r --release Build and test release APK, instead of debug
|
||||
END
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,20 +0,0 @@
|
||||
VERSION_CODE = 20000
|
||||
VERSION_NAME = 2.0.0-alpha
|
||||
|
||||
MIN_SDK_VERSION = 23
|
||||
TARGET_SDK_VERSION = 29
|
||||
COMPILE_SDK_VERSION = 29
|
||||
|
||||
DAGGER_VERSION = 2.25.4
|
||||
KOTLIN_VERSION = 1.4.0
|
||||
KX_COROUTINES_VERSION = 1.4.2
|
||||
SUPPORT_LIBRARY_VERSION = 28.0.0
|
||||
AUTO_FACTORY_VERSION = 1.0-beta6
|
||||
BUILD_TOOLS_VERSION = 4.1.0
|
||||
KTOR_VERSION=1.4.2
|
||||
|
||||
org.gradle.parallel=false
|
||||
org.gradle.daemon=true
|
||||
org.gradle.jvmargs=-Xms2048m -Xmx2048m -XX:MaxPermSize=2048m
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@@ -1,6 +0,0 @@
|
||||
#Sat Nov 28 09:55:24 CST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
|
||||
@@ -1 +0,0 @@
|
||||
uhabits-android/src/main/play/
|
||||
@@ -1 +0,0 @@
|
||||
include ':uhabits-android', ':uhabits-core', ':android-base', ':android-pickers'
|
||||
@@ -1,157 +0,0 @@
|
||||
"""
|
||||
Generate coverage badges for Coverage.py.
|
||||
Forked from https://github.com/dbrgn/coverage-badge
|
||||
"""
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import pkg_resources
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
__version__ = '0.2.0-uhabits'
|
||||
|
||||
|
||||
DEFAULT_COLOR = '#a4a61d'
|
||||
COLORS = {
|
||||
'brightgreen': '#4c1',
|
||||
'green': '#97CA00',
|
||||
'yellowgreen': '#a4a61d',
|
||||
'yellow': '#dfb317',
|
||||
'orange': '#fe7d37',
|
||||
'red': '#e05d44',
|
||||
'lightgrey': '#9f9f9f',
|
||||
}
|
||||
|
||||
COLOR_RANGES = [
|
||||
(95, 'brightgreen'),
|
||||
(90, 'green'),
|
||||
(75, 'yellowgreen'),
|
||||
(60, 'yellow'),
|
||||
(40, 'orange'),
|
||||
(0, 'red'),
|
||||
]
|
||||
|
||||
|
||||
class Devnull(object):
|
||||
"""
|
||||
A file like object that does nothing.
|
||||
"""
|
||||
def write(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def get_total(report):
|
||||
missed = 0
|
||||
covered = 0
|
||||
for r in report.split(":"):
|
||||
doc = BeautifulSoup(open(r), 'xml')
|
||||
tag = doc.select("report > counter[type^INST]")[0]
|
||||
missed = missed + float(tag['missed'])
|
||||
covered = covered + float(tag['covered'])
|
||||
return str(int(round(100 * covered / (missed + covered))))
|
||||
|
||||
def get_color(total):
|
||||
"""
|
||||
Return color for current coverage percent
|
||||
"""
|
||||
try:
|
||||
xtotal = int(total)
|
||||
except ValueError:
|
||||
return COLORS['lightgrey']
|
||||
for range_, color in COLOR_RANGES:
|
||||
if xtotal >= range_:
|
||||
return COLORS[color]
|
||||
|
||||
|
||||
def get_badge(total, color=DEFAULT_COLOR):
|
||||
"""
|
||||
Read the SVG template from the package, update total, return SVG as a
|
||||
string.
|
||||
"""
|
||||
template_path = os.path.join('templates', 'flat.svg')
|
||||
template = pkg_resources.resource_string(__name__, template_path).decode('utf8')
|
||||
return template.replace('{{ total }}', total).replace('{{ color }}', color)
|
||||
|
||||
|
||||
def parse_args(argv=None):
|
||||
"""
|
||||
Parse the command line arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-o', dest='filepath',
|
||||
help='Save the file to the specified path.')
|
||||
parser.add_argument('-p', dest='plain_color', action='store_true',
|
||||
help='Plain color mode. Standard green badge.')
|
||||
parser.add_argument('-f', dest='force', action='store_true',
|
||||
help='Force overwrite image, use with -o key.')
|
||||
parser.add_argument('-q', dest='quiet', action='store_true',
|
||||
help='Don\'t output any non-error messages.')
|
||||
parser.add_argument('-v', dest='print_version', action='store_true',
|
||||
help='Show version.')
|
||||
parser.add_argument('-i', dest='reportFilename',
|
||||
help='Jacoco report')
|
||||
|
||||
# If arguments have been passed in, use them.
|
||||
if argv:
|
||||
return parser.parse_args(argv)
|
||||
|
||||
# Otherwise, just use sys.argv directly.
|
||||
else:
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def save_badge(badge, filepath, force=False):
|
||||
"""
|
||||
Save badge to the specified path.
|
||||
"""
|
||||
# Validate path (part 1)
|
||||
if filepath.endswith('/'):
|
||||
print('Error: Filepath may not be a directory.')
|
||||
sys.exit(1)
|
||||
|
||||
# Get absolute filepath
|
||||
path = os.path.abspath(filepath)
|
||||
if not path.lower().endswith('.svg'):
|
||||
path += '.svg'
|
||||
|
||||
# Validate path (part 2)
|
||||
if not force and os.path.exists(path):
|
||||
print('Error: "{}" already exists.'.format(path))
|
||||
sys.exit(1)
|
||||
|
||||
# Write file
|
||||
with open(path, 'w') as f:
|
||||
f.write(badge)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
"""
|
||||
Console scripts entry point.
|
||||
"""
|
||||
args = parse_args(argv)
|
||||
|
||||
# Print version
|
||||
if args.print_version:
|
||||
print('coverage-badge v{}'.format(__version__))
|
||||
sys.exit(0)
|
||||
|
||||
total = get_total(args.reportFilename)
|
||||
color = DEFAULT_COLOR if args.plain_color else get_color(total)
|
||||
badge = get_badge(total, color)
|
||||
|
||||
# Show or save output
|
||||
if args.filepath:
|
||||
path = save_badge(badge, args.filepath, args.force)
|
||||
if not args.quiet:
|
||||
print('Saved badge to {}'.format(path))
|
||||
else:
|
||||
print(badge, end='')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="99" height="20">
|
||||
<linearGradient id="b" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<mask id="a">
|
||||
<rect width="99" height="20" rx="3" fill="#fff"/>
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<path fill="#555" d="M0 0h63v20H0z"/>
|
||||
<path fill="{{ color }}" d="M63 0h36v20H63z"/>
|
||||
<path fill="url(#b)" d="M0 0h99v20H0z"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||
<text x="31.5" y="14">coverage</text>
|
||||
<text x="80" y="15" fill="#010101" fill-opacity=".3">{{ total }}%</text>
|
||||
<text x="80" y="14">{{ total }}%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 926 B |
@@ -1,156 +0,0 @@
|
||||
plugins {
|
||||
id 'idea'
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
id 'com.github.triplet.play' version '2.6.2'
|
||||
id 'kotlin-android-extensions'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION as Integer
|
||||
|
||||
def secretPropsFile = file("../../.secret/gradle.properties")
|
||||
if (secretPropsFile.exists()) {
|
||||
def secrets = new Properties()
|
||||
secretPropsFile.withInputStream { secrets.load(it) }
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file(secrets.LOOP_KEY_STORE)
|
||||
storePassword secrets.LOOP_STORE_PASSWORD
|
||||
keyAlias secrets.LOOP_KEY_ALIAS
|
||||
keyPassword secrets.LOOP_KEY_PASSWORD
|
||||
}
|
||||
}
|
||||
buildTypes.release.signingConfig signingConfigs.release
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode VERSION_CODE as Integer
|
||||
versionName "$VERSION_NAME"
|
||||
minSdkVersion MIN_SDK_VERSION as Integer
|
||||
targetSdkVersion TARGET_SDK_VERSION as Integer
|
||||
|
||||
applicationId "org.isoron.uhabits"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
debug {
|
||||
testCoverageEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
disable 'GoogleAppIndexingWarning'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen { false }
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.assets.srcDirs += '../uhabits-core/src/main/resources/'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":uhabits-core")
|
||||
implementation project(":android-base")
|
||||
implementation project(":android-pickers")
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation "com.github.paolorotolo:appintro:3.4.0"
|
||||
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||
implementation "com.jakewharton:butterknife:8.6.1-SNAPSHOT"
|
||||
implementation "org.apmem.tools:layouts:1.10"
|
||||
implementation "com.google.code.gson:gson:2.8.5"
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KX_COROUTINES_VERSION"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$KX_COROUTINES_VERSION"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
|
||||
implementation 'com.google.zxing:core:3.4.1'
|
||||
implementation "io.ktor:ktor-client-core:$KTOR_VERSION"
|
||||
implementation "io.ktor:ktor-client-android:$KTOR_VERSION"
|
||||
implementation "io.ktor:ktor-client-json:$KTOR_VERSION"
|
||||
implementation "io.ktor:ktor-client-jackson:$KTOR_VERSION"
|
||||
implementation "com.google.guava:guava:30.0-android"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
compileOnly "javax.annotation:jsr250-api:1.0"
|
||||
compileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
|
||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||
kapt "com.jakewharton:butterknife-compiler:10.2.1"
|
||||
annotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
|
||||
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
|
||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||
androidTestImplementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||
androidTestImplementation "com.linkedin.testbutler:test-butler-library:1.3.1"
|
||||
androidTestCompileOnly "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
|
||||
androidTestAnnotationProcessor "com.google.auto.factory:auto-factory:$AUTO_FACTORY_VERSION"
|
||||
androidTestImplementation 'androidx.annotation:annotation:1.0.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation "io.ktor:ktor-client-mock:$KTOR_VERSION"
|
||||
androidTestImplementation "io.ktor:ktor-jackson:$KTOR_VERSION"
|
||||
androidTestImplementation project(":uhabits-core")
|
||||
kaptAndroidTest "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||
|
||||
// mockito-android 2+ includes net.bytebuddy, which causes tests to fail.
|
||||
// Excluding the package net.bytebuddy on AndroidManifest.xml breaks some
|
||||
// AndroidJUnitRunner functionality, such as running individual methods.
|
||||
androidTestImplementation "org.mockito:mockito-core:1.10.19"
|
||||
androidTestImplementation "com.google.dexmaker:dexmaker-mockito:1.2"
|
||||
|
||||
testImplementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||
testImplementation "org.mockito:mockito-core:2.8.9"
|
||||
testImplementation "org.mockito:mockito-inline:2.8.9"
|
||||
testImplementation "junit:junit:4.12"
|
||||
|
||||
implementation('com.opencsv:opencsv:3.10') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
}
|
||||
implementation('io.socket:socket.io-client:0.8.3') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
||||
play {
|
||||
serviceAccountCredentials = file("../../.secret/gcp-key.json")
|
||||
track = "alpha"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
</lint>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.appwidget.*;
|
||||
import android.content.*;
|
||||
import android.content.res.*;
|
||||
import android.os.*;
|
||||
import android.util.*;
|
||||
|
||||
import androidx.annotation.*;
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.platform.app.*;
|
||||
import androidx.test.uiautomator.*;
|
||||
|
||||
import junit.framework.*;
|
||||
|
||||
import org.isoron.androidbase.*;
|
||||
import org.isoron.androidbase.activities.*;
|
||||
import org.isoron.androidbase.utils.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
import org.junit.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.*;
|
||||
import static androidx.test.uiautomator.UiDevice.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
@MediumTest
|
||||
public class BaseAndroidTest extends TestCase
|
||||
{
|
||||
// 8:00am, January 25th, 2015 (UTC)
|
||||
public static final long FIXED_LOCAL_TIME = 1422172800000L;
|
||||
|
||||
protected Context testContext;
|
||||
|
||||
protected Context targetContext;
|
||||
|
||||
protected Preferences prefs;
|
||||
|
||||
protected HabitList habitList;
|
||||
|
||||
protected TaskRunner taskRunner;
|
||||
|
||||
protected HabitFixtures fixtures;
|
||||
|
||||
protected CountDownLatch latch;
|
||||
|
||||
protected HabitsApplicationTestComponent appComponent;
|
||||
|
||||
protected ModelFactory modelFactory;
|
||||
|
||||
protected HabitsActivityTestComponent component;
|
||||
|
||||
private boolean isDone = false;
|
||||
|
||||
private UiDevice device;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
device = getInstance(getInstrumentation());
|
||||
|
||||
targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
testContext = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
|
||||
DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME);
|
||||
DateUtils.setStartDayOffset(0, 0);
|
||||
setResolution(2.0f);
|
||||
setTheme(R.style.AppBaseTheme);
|
||||
setLocale("en", "US");
|
||||
|
||||
latch = new CountDownLatch(1);
|
||||
|
||||
appComponent = DaggerHabitsApplicationTestComponent
|
||||
.builder()
|
||||
.appContextModule(new AppContextModule(targetContext.getApplicationContext()))
|
||||
.build();
|
||||
|
||||
HabitsApplication.Companion.setComponent(appComponent);
|
||||
prefs = appComponent.getPreferences();
|
||||
habitList = appComponent.getHabitList();
|
||||
taskRunner = appComponent.getTaskRunner();
|
||||
modelFactory = appComponent.getModelFactory();
|
||||
|
||||
prefs.clear();
|
||||
|
||||
fixtures = new HabitFixtures(modelFactory, habitList);
|
||||
fixtures.purgeHabits(appComponent.getHabitList());
|
||||
Habit habit = fixtures.createEmptyHabit();
|
||||
|
||||
component = DaggerHabitsActivityTestComponent
|
||||
.builder()
|
||||
.activityContextModule(new ActivityContextModule(targetContext))
|
||||
.habitsApplicationComponent(appComponent)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected void assertWidgetProviderIsInstalled(Class componentClass)
|
||||
{
|
||||
ComponentName provider =
|
||||
new ComponentName(targetContext, componentClass);
|
||||
AppWidgetManager manager = AppWidgetManager.getInstance(targetContext);
|
||||
|
||||
List<ComponentName> installedProviders = new LinkedList<>();
|
||||
for (AppWidgetProviderInfo info : manager.getInstalledProviders())
|
||||
installedProviders.add(info.provider);
|
||||
|
||||
assertThat(installedProviders, hasItems(provider));
|
||||
}
|
||||
|
||||
protected void awaitLatch() throws InterruptedException
|
||||
{
|
||||
assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
protected void setLocale(@NonNull String language, @NonNull String country)
|
||||
{
|
||||
Locale locale = new Locale(language, country);
|
||||
Locale.setDefault(locale);
|
||||
Resources res = targetContext.getResources();
|
||||
Configuration config = res.getConfiguration();
|
||||
config.setLocale(locale);
|
||||
}
|
||||
|
||||
protected void setResolution(float r)
|
||||
{
|
||||
DisplayMetrics dm = targetContext.getResources().getDisplayMetrics();
|
||||
dm.density = r;
|
||||
dm.scaledDensity = r;
|
||||
InterfaceUtils.setFixedResolution(r);
|
||||
}
|
||||
|
||||
protected void runConcurrently(Runnable... runnableList) throws Exception
|
||||
{
|
||||
isDone = false;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||
List<Future> futures = new LinkedList<>();
|
||||
for (Runnable r : runnableList)
|
||||
futures.add(executor.submit(() ->
|
||||
{
|
||||
while (!isDone) r.run();
|
||||
return null;
|
||||
}));
|
||||
|
||||
Thread.sleep(3000);
|
||||
isDone = true;
|
||||
executor.shutdown();
|
||||
for(Future f : futures) f.get();
|
||||
while (!executor.isTerminated()) Thread.sleep(50);
|
||||
}
|
||||
|
||||
protected void setTheme(@StyleRes int themeId)
|
||||
{
|
||||
targetContext.setTheme(themeId);
|
||||
StyledResources.setFixedTheme(themeId);
|
||||
}
|
||||
|
||||
protected void sleep(int time)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(time);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
public long timestamp(int year, int month, int day)
|
||||
{
|
||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||
cal.set(year, month, day);
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
protected void startTracing()
|
||||
{
|
||||
File dir = new AndroidDirFinder(targetContext).getFilesDir("Profile");
|
||||
assertNotNull(dir);
|
||||
String tracePath = dir.getAbsolutePath() + "/performance.trace";
|
||||
Log.d("PerformanceTest", String.format("Saving trace file to %s", tracePath));
|
||||
Debug.startMethodTracingSampling(tracePath, 0, 1000);
|
||||
}
|
||||
|
||||
protected void stopTracing()
|
||||
{
|
||||
Debug.stopMethodTracing();
|
||||
}
|
||||
|
||||
protected Timestamp day(int offset)
|
||||
{
|
||||
return DateUtils.getToday().minus(offset);
|
||||
}
|
||||
|
||||
|
||||
public void setSystemTime(String tz,
|
||||
int year,
|
||||
int javaMonth,
|
||||
int day,
|
||||
int hourOfDay,
|
||||
int minute) throws Exception
|
||||
{
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(year, javaMonth, day, hourOfDay, minute);
|
||||
cal.setTimeZone(TimeZone.getTimeZone(tz));
|
||||
setSystemTime(cal);
|
||||
}
|
||||
|
||||
private void setSystemTime(GregorianCalendar cal) throws Exception
|
||||
{
|
||||
ZoneId tz = cal.getTimeZone().toZoneId();
|
||||
|
||||
// Set time zone (temporary)
|
||||
String command = String.format("service call alarm 3 s16 %s", tz);
|
||||
device.executeShellCommand(command);
|
||||
|
||||
// Set time zone (permanent)
|
||||
command = String.format("setprop persist.sys.timezone %s", tz);
|
||||
device.executeShellCommand(command);
|
||||
|
||||
// Set time
|
||||
String date = String.format("%02d%02d%02d%02d%02d.%02d",
|
||||
cal.get(Calendar.MONTH) + 1,
|
||||
cal.get(Calendar.DAY_OF_MONTH),
|
||||
cal.get(Calendar.HOUR_OF_DAY),
|
||||
cal.get(Calendar.MINUTE),
|
||||
cal.get(Calendar.YEAR),
|
||||
cal.get(Calendar.SECOND));
|
||||
|
||||
// Set time (method 1)
|
||||
// Run twice to override daylight saving time
|
||||
device.executeShellCommand("date " + date);
|
||||
device.executeShellCommand("date " + date);
|
||||
|
||||
// Set time (method 2)
|
||||
// Run in addition to the method above because one of these mail fail, depending
|
||||
// on the Android API version.
|
||||
command = String.format("date -u @%d", cal.getTimeInMillis() / 1000);
|
||||
device.executeShellCommand(command);
|
||||
|
||||
// Wait for system events to settle
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
private GregorianCalendar savedCalendar = null;
|
||||
|
||||
public void saveSystemTime()
|
||||
{
|
||||
savedCalendar = new GregorianCalendar();
|
||||
}
|
||||
|
||||
public void restoreSystemTime() throws Exception
|
||||
{
|
||||
if (savedCalendar == null) throw new NullPointerException();
|
||||
setSystemTime(savedCalendar);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.content.*;
|
||||
|
||||
import androidx.test.uiautomator.*;
|
||||
|
||||
import com.linkedin.android.testbutler.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
import org.junit.*;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
import static androidx.test.core.app.ApplicationProvider.*;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.*;
|
||||
import static androidx.test.uiautomator.UiDevice.*;
|
||||
|
||||
public class BaseUserInterfaceTest
|
||||
{
|
||||
private static final String PKG = "org.isoron.uhabits";
|
||||
public static final String EMPTY_DESCRIPTION_HABIT_NAME = "Read books";
|
||||
|
||||
public static UiDevice device;
|
||||
|
||||
private HabitsApplicationComponent component;
|
||||
|
||||
private HabitList habitList;
|
||||
|
||||
private Preferences prefs;
|
||||
|
||||
private HabitFixtures fixtures;
|
||||
|
||||
private HabitCardListCache cache;
|
||||
|
||||
public static void startActivity(Class cls)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(PKG, cls.getCanonicalName()));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getApplicationContext().startActivity(intent);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
device = getInstance(getInstrumentation());
|
||||
TestButler.setup(getApplicationContext());
|
||||
TestButler.verifyAnimationsDisabled(getApplicationContext());
|
||||
|
||||
HabitsApplication app =
|
||||
(HabitsApplication) getApplicationContext().getApplicationContext();
|
||||
component = app.getComponent();
|
||||
habitList = component.getHabitList();
|
||||
prefs = component.getPreferences();
|
||||
cache = component.getHabitCardListCache();
|
||||
fixtures = new HabitFixtures(component.getModelFactory(), habitList);
|
||||
resetState();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception
|
||||
{
|
||||
for (int i = 0; i < 10; i++) device.pressBack();
|
||||
TestButler.teardown(getApplicationContext());
|
||||
}
|
||||
|
||||
private void resetState() throws Exception
|
||||
{
|
||||
prefs.clear();
|
||||
prefs.setFirstRun(false);
|
||||
prefs.updateLastHint(100, DateUtils.getToday());
|
||||
habitList.removeAll();
|
||||
cache.refreshAllHabits();
|
||||
Thread.sleep(1000);
|
||||
|
||||
Habit h1 = fixtures.createEmptyHabit();
|
||||
h1.setName("Wake up early");
|
||||
h1.setQuestion("Did you wake up early today?");
|
||||
h1.setDescription("test description 1");
|
||||
h1.setColor(5);
|
||||
habitList.update(h1);
|
||||
|
||||
Habit h2 = fixtures.createShortHabit();
|
||||
h2.setName("Track time");
|
||||
h2.setQuestion("Did you track your time?");
|
||||
h2.setDescription("test description 2");
|
||||
h2.setColor(5);
|
||||
habitList.update(h2);
|
||||
|
||||
Habit h3 = fixtures.createLongHabit();
|
||||
h3.setName("Meditate");
|
||||
h3.setQuestion("Did meditate today?");
|
||||
h3.setDescription("test description 3");
|
||||
h3.setColor(10);
|
||||
habitList.update(h3);
|
||||
|
||||
Habit h4 = fixtures.createEmptyHabit();
|
||||
h4.setName(EMPTY_DESCRIPTION_HABIT_NAME);
|
||||
h4.setQuestion("Did you read books today?");
|
||||
h4.setDescription("");
|
||||
h4.setColor(2);
|
||||
habitList.update(h4);
|
||||
}
|
||||
|
||||
protected void rotateDevice() throws Exception
|
||||
{
|
||||
device.setOrientationLeft();
|
||||
device.setOrientationNatural();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import android.graphics.*;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
|
||||
import androidx.annotation.*;
|
||||
import androidx.test.platform.app.*;
|
||||
|
||||
import org.isoron.androidbase.*;
|
||||
import org.isoron.androidbase.utils.*;
|
||||
import org.isoron.uhabits.widgets.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static android.view.View.MeasureSpec.*;
|
||||
|
||||
public class BaseViewTest extends BaseAndroidTest
|
||||
{
|
||||
public double similarityCutoff = 0.00018;
|
||||
|
||||
@Override
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
protected void assertRenders(View view, String expectedImagePath)
|
||||
throws IOException
|
||||
{
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
expectedImagePath = "views/" + expectedImagePath;
|
||||
Bitmap actual = renderView(view);
|
||||
if(actual == null) throw new IllegalStateException("actual is null");
|
||||
|
||||
try
|
||||
{
|
||||
Bitmap expected = getBitmapFromAssets(expectedImagePath);
|
||||
double distance = distance(actual, expected);
|
||||
if (distance > similarityCutoff)
|
||||
{
|
||||
saveBitmap(expectedImagePath, ".expected", expected);
|
||||
String path = saveBitmap(expectedImagePath, "", actual);
|
||||
fail(String.format("Image differs from expected " +
|
||||
"(distance=%f). Actual rendered " +
|
||||
"image saved to %s", distance, path));
|
||||
}
|
||||
|
||||
expected.recycle();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
String path = saveBitmap(expectedImagePath, "", actual);
|
||||
fail(String.format("Could not open expected image. Actual " +
|
||||
"rendered image saved to %s", path));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected FrameLayout convertToView(BaseWidget widget,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
widget.setDimensions(
|
||||
new WidgetDimensions(width, height, width, height));
|
||||
FrameLayout view = new FrameLayout(targetContext);
|
||||
RemoteViews remoteViews = widget.getPortraitRemoteViews();
|
||||
view.addView(remoteViews.apply(targetContext, view));
|
||||
measureView(view, width, height);
|
||||
return view;
|
||||
}
|
||||
|
||||
protected float dpToPixels(int dp)
|
||||
{
|
||||
return InterfaceUtils.dpToPixels(targetContext, dp);
|
||||
}
|
||||
|
||||
protected void measureView(View view, float width, float height)
|
||||
{
|
||||
int specWidth = makeMeasureSpec((int) width, View.MeasureSpec.EXACTLY);
|
||||
int specHeight = makeMeasureSpec((int) height, View.MeasureSpec.EXACTLY);
|
||||
|
||||
view.setLayoutParams(new ViewGroup.LayoutParams((int) width, (int) height));
|
||||
view.measure(specWidth, specHeight);
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
}
|
||||
|
||||
protected void skipAnimation(View view)
|
||||
{
|
||||
ViewPropertyAnimator animator = view.animate();
|
||||
animator.setDuration(0);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private int[] colorToArgb(int c1)
|
||||
{
|
||||
return new int[]{
|
||||
(c1 >> 24) & 0xff, //alpha
|
||||
(c1 >> 16) & 0xff, //red
|
||||
(c1 >> 8) & 0xff, //green
|
||||
(c1) & 0xff //blue
|
||||
};
|
||||
}
|
||||
|
||||
private double distance(Bitmap b1, Bitmap b2)
|
||||
{
|
||||
if (b1.getWidth() != b2.getWidth()) return 1.0;
|
||||
if (b1.getHeight() != b2.getHeight()) return 1.0;
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
double distance = 0.0;
|
||||
for (int x = 0; x < b1.getWidth(); x++)
|
||||
{
|
||||
for (int y = 0; y < b1.getHeight(); y++)
|
||||
{
|
||||
if (random.nextInt(4) != 0) continue;
|
||||
|
||||
int[] argb1 = colorToArgb(b1.getPixel(x, y));
|
||||
int[] argb2 = colorToArgb(b2.getPixel(x, y));
|
||||
distance += Math.abs(argb1[0] - argb2[0]);
|
||||
distance += Math.abs(argb1[1] - argb2[1]);
|
||||
distance += Math.abs(argb1[2] - argb2[2]);
|
||||
distance += Math.abs(argb1[3] - argb2[3]);
|
||||
}
|
||||
}
|
||||
|
||||
distance /= (0xff * 16) * b1.getWidth() * b1.getHeight();
|
||||
return distance;
|
||||
}
|
||||
|
||||
private Bitmap getBitmapFromAssets(String path) throws IOException
|
||||
{
|
||||
InputStream stream = testContext.getAssets().open(path);
|
||||
return BitmapFactory.decodeStream(stream);
|
||||
}
|
||||
|
||||
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
|
||||
throws IOException
|
||||
{
|
||||
File dir = FileUtils.getSDCardDir("test-screenshots");
|
||||
if (dir == null)
|
||||
dir = new AndroidDirFinder(targetContext).getFilesDir("test-screenshots");
|
||||
if (dir == null) throw new RuntimeException(
|
||||
"Could not find suitable dir for screenshots");
|
||||
|
||||
filename = filename.replaceAll("\\.png$", suffix + ".png");
|
||||
String absolutePath =
|
||||
String.format("%s/%s", dir.getAbsolutePath(), filename);
|
||||
|
||||
File parent = new File(absolutePath).getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) throw new RuntimeException(
|
||||
String.format("Could not create dir: %s",
|
||||
parent.getAbsolutePath()));
|
||||
|
||||
FileOutputStream out = new FileOutputStream(absolutePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
public Bitmap renderView(View view)
|
||||
{
|
||||
int width = view.getMeasuredWidth();
|
||||
int height = view.getMeasuredHeight();
|
||||
if(view.isLayoutRequested())
|
||||
measureView(view, width, height);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
view.invalidate();
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.DateUtils;
|
||||
|
||||
import static org.isoron.uhabits.core.models.Checkmark.*;
|
||||
|
||||
public class HabitFixtures
|
||||
{
|
||||
public boolean LONG_HABIT_CHECKS[] = {
|
||||
true, false, false, true, true, true, false, false, true, true
|
||||
};
|
||||
|
||||
public int LONG_NUMERICAL_HABIT_CHECKS[] = {
|
||||
200000, 0, 150000, 137000, 0, 0, 500000, 30000, 100000, 0, 300000,
|
||||
100000, 0, 100000
|
||||
};
|
||||
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
private final HabitList habitList;
|
||||
|
||||
public HabitFixtures(ModelFactory modelFactory, HabitList habitList)
|
||||
{
|
||||
this.modelFactory = modelFactory;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
public Habit createEmptyHabit()
|
||||
{
|
||||
return createEmptyHabit(null);
|
||||
}
|
||||
|
||||
public Habit createEmptyHabit(Long id)
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Meditate");
|
||||
habit.setQuestion("Did you meditate this morning?");
|
||||
habit.setDescription("This is a test description");
|
||||
habit.setColor(5);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
habit.setId(id);
|
||||
habitList.add(habit);
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createLongHabit()
|
||||
{
|
||||
Habit habit = createEmptyHabit();
|
||||
habit.setFrequency(new Frequency(3, 7));
|
||||
habit.setColor(7);
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
|
||||
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
|
||||
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
|
||||
|
||||
for (int mark : marks)
|
||||
habit.getRepetitions().setValue(today.minus(mark), YES_MANUAL);
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createVeryLongHabit()
|
||||
{
|
||||
Habit habit = createEmptyHabit();
|
||||
habit.setFrequency(new Frequency(1, 2));
|
||||
habit.setColor(11);
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
int marks[] = {0, 3, 5, 6, 7, 10, 13, 14, 15, 18, 21, 22, 23, 24, 27, 28, 30, 31, 34, 37,
|
||||
39, 42, 43, 46, 47, 48, 51, 52, 54, 55, 57, 59, 62, 65, 68, 71, 73, 76, 79,
|
||||
80, 81, 83, 85, 86, 89, 90, 91, 94, 96, 98, 100, 103, 104, 106, 109, 111,
|
||||
112, 113, 115, 117, 120, 123, 126, 129, 132, 134, 136, 139, 141, 142, 145,
|
||||
148, 149, 151, 152, 154, 156, 157, 159, 161, 162, 163, 164, 166, 168, 170,
|
||||
172, 173, 174, 175, 176, 178, 180, 181, 184, 185, 188, 189, 190, 191, 194,
|
||||
195, 197, 198, 199, 200, 202, 205, 208, 211, 213, 215, 216, 218, 220, 222,
|
||||
223, 225, 227, 228, 230, 231, 232, 234, 235, 238, 241, 242, 244, 247, 250,
|
||||
251, 253, 254, 257, 260, 261, 263, 264, 266, 269, 272, 273, 276, 279, 281,
|
||||
284, 285, 288, 291, 292, 294, 296, 297, 299, 300, 301, 303, 306, 307, 308,
|
||||
309, 310, 313, 316, 319, 322, 324, 326, 329, 330, 332, 334, 335, 337, 338,
|
||||
341, 344, 345, 346, 347, 350, 352, 355, 358, 360, 361, 362, 363, 365, 368,
|
||||
371, 373, 374, 376, 379, 380, 382, 384, 385, 387, 389, 390, 392, 393, 395,
|
||||
396, 399, 401, 404, 407, 410, 411, 413, 414, 416, 417, 419, 420, 423, 424,
|
||||
427, 429, 431, 433, 436, 439, 440, 442, 445, 447, 450, 453, 454, 456, 459,
|
||||
460, 461, 464, 466, 468, 470, 473, 474, 475, 477, 479, 481, 482, 483, 486,
|
||||
489, 491, 493, 495, 497, 498, 500, 503, 504, 507, 510, 511, 512, 515, 518,
|
||||
519, 521, 522, 525, 528, 531, 532, 534, 537, 539, 541, 543, 544, 547, 550,
|
||||
551, 554, 556, 557, 560, 561, 564, 567, 568, 569, 570, 572, 575, 576, 579,
|
||||
582, 583, 584, 586, 589};
|
||||
|
||||
for (int mark : marks)
|
||||
habit.getRepetitions().setValue(today.minus(mark), YES_MANUAL);
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createLongNumericalHabit()
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Read");
|
||||
habit.setQuestion("How many pages did you walk today?");
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(200.0);
|
||||
habit.setUnit("pages");
|
||||
habitList.add(habit);
|
||||
|
||||
Timestamp timestamp = DateUtils.getToday();
|
||||
for (int value : LONG_NUMERICAL_HABIT_CHECKS)
|
||||
{
|
||||
habit.getRepetitions().setValue(timestamp, value);
|
||||
timestamp = timestamp.minus(1);
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createShortHabit()
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Wake up early");
|
||||
habit.setQuestion("Did you wake up before 6am?");
|
||||
habit.setFrequency(new Frequency(2, 3));
|
||||
habitList.add(habit);
|
||||
|
||||
Timestamp timestamp = DateUtils.getToday();
|
||||
for (boolean c : LONG_HABIT_CHECKS)
|
||||
{
|
||||
if (c) habit.getRepetitions().setValue(timestamp, YES_MANUAL);
|
||||
timestamp = timestamp.minus(1);
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public synchronized void purgeHabits(HabitList habitList)
|
||||
{
|
||||
habitList.removeAll();
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits
|
||||
|
||||
import dagger.*
|
||||
import org.isoron.androidbase.activities.*
|
||||
import org.isoron.uhabits.activities.*
|
||||
import org.isoron.uhabits.activities.habits.list.*
|
||||
import org.isoron.uhabits.activities.habits.list.views.*
|
||||
import org.isoron.uhabits.activities.habits.show.*
|
||||
import org.isoron.uhabits.core.ui.screens.habits.list.*
|
||||
import org.mockito.Mockito.*
|
||||
|
||||
@Module
|
||||
class TestModule {
|
||||
@Provides fun ListHabitsBehavior() = mock(ListHabitsBehavior::class.java)
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Component(modules = arrayOf(
|
||||
ActivityContextModule::class,
|
||||
HabitsActivityModule::class,
|
||||
ListHabitsModule::class,
|
||||
ShowHabitModule::class,
|
||||
HabitModule::class,
|
||||
TestModule::class
|
||||
), dependencies = arrayOf(HabitsApplicationComponent::class))
|
||||
interface HabitsActivityTestComponent {
|
||||
fun getCheckmarkPanelViewFactory(): CheckmarkPanelViewFactory
|
||||
fun getHabitCardViewFactory(): HabitCardViewFactory
|
||||
fun getCheckmarkButtonViewFactory(): CheckmarkButtonViewFactory
|
||||
fun getNumberButtonViewFactory(): NumberButtonViewFactory
|
||||
fun getNumberPanelViewFactory(): NumberPanelViewFactory
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.androidbase.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class HabitsApplicationTest extends BaseAndroidTest
|
||||
{
|
||||
@Test
|
||||
public void test_getLogcat() throws IOException
|
||||
{
|
||||
String msg = "LOGCAT TEST";
|
||||
new RuntimeException(msg).printStackTrace();
|
||||
|
||||
String log = new AndroidBugReporter(targetContext).getLogcat();
|
||||
assertThat(log, containsString(msg));
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import static org.isoron.uhabits.acceptance.steps.CommonSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class AboutTest extends BaseUserInterfaceTest
|
||||
{
|
||||
@Test
|
||||
public void shouldDisplayAboutScreen() {
|
||||
launchApp();
|
||||
clickMenu(ABOUT);
|
||||
verifyDisplaysText("Loop Habit Tracker");
|
||||
verifyDisplaysText("Rate this app on Google Play");
|
||||
verifyDisplaysText("Developers");
|
||||
verifyDisplaysText("Translators");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisplayAboutScreenFromSettings() {
|
||||
launchApp();
|
||||
clickMenu(SETTINGS);
|
||||
clickText("About");
|
||||
verifyDisplaysText("Translators");
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import static org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.CommonSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.EditHabitSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class HabitsTest extends BaseUserInterfaceTest
|
||||
{
|
||||
@Test
|
||||
public void shouldCreateHabit() throws Exception {
|
||||
shouldCreateHabit("this is a test description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateHabitBlankDescription() throws Exception {
|
||||
shouldCreateHabit("");
|
||||
}
|
||||
|
||||
private void shouldCreateHabit(String description) throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
clickMenu(ADD);
|
||||
|
||||
verifyShowsScreen(SELECT_HABIT_TYPE);
|
||||
clickText("Yes or No");
|
||||
|
||||
verifyShowsScreen(EDIT_HABIT);
|
||||
String testName = "Hello world";
|
||||
typeName(testName);
|
||||
typeQuestion("Did you say hello to the world today?");
|
||||
typeDescription(description);
|
||||
pickFrequency();
|
||||
pickColor(5);
|
||||
clickSave();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
verifyDisplaysText(testName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowHabitStatistics() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
clickText("Track time");
|
||||
|
||||
verifyShowsScreen(SHOW_HABIT);
|
||||
verifyDisplayGraphs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeleteHabit() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
longClickText("Track time");
|
||||
clickMenu(DELETE);
|
||||
clickOK();
|
||||
verifyDoesNotDisplayText("Track time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEditHabit() throws Exception {
|
||||
shouldEditHabit("this is a test description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEditHabitBlankDescription() throws Exception {
|
||||
shouldEditHabit("");
|
||||
}
|
||||
|
||||
private void shouldEditHabit(String description) throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
longClickText("Track time");
|
||||
clickMenu(EDIT);
|
||||
|
||||
verifyShowsScreen(EDIT_HABIT);
|
||||
typeName("Take a walk");
|
||||
typeQuestion("Did you take a walk today?");
|
||||
typeDescription(description);
|
||||
clickSave();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
verifyDisplaysTextInSequence("Wake up early", "Take a walk", "Meditate");
|
||||
verifyDoesNotDisplayText("Track time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEditHabit_fromStatisticsScreen() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
clickText("Track time");
|
||||
|
||||
verifyShowsScreen(SHOW_HABIT);
|
||||
clickMenu(EDIT);
|
||||
|
||||
verifyShowsScreen(EDIT_HABIT);
|
||||
typeName("Take a walk");
|
||||
typeQuestion("Did you take a walk today?");
|
||||
pickColor(10);
|
||||
clickSave();
|
||||
|
||||
verifyShowsScreen(SHOW_HABIT);
|
||||
verifyDisplaysText("Take a walk");
|
||||
pressBack();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
verifyDisplaysText("Take a walk");
|
||||
verifyDoesNotDisplayText("Track time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldArchiveAndUnarchiveHabits() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
longClickText("Track time");
|
||||
clickMenu(ARCHIVE);
|
||||
verifyDoesNotDisplayText("Track time");
|
||||
clickMenu(TOGGLE_ARCHIVED);
|
||||
verifyDisplaysText("Track time");
|
||||
|
||||
longClickText("Track time");
|
||||
clickMenu(UNARCHIVE);
|
||||
clickMenu(TOGGLE_ARCHIVED);
|
||||
verifyDisplaysText("Track time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToggleCheckmarksAndUpdateScore() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
longPressCheckmarks("Wake up early", 2);
|
||||
clickText("Wake up early");
|
||||
|
||||
verifyShowsScreen(SHOW_HABIT);
|
||||
verifyDisplaysText("10%");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHideCompleted() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
verifyShowsScreen(LIST_HABITS);
|
||||
verifyDisplaysText("Track time");
|
||||
verifyDisplaysText("Wake up early");
|
||||
|
||||
clickMenu(TOGGLE_COMPLETED);
|
||||
verifyDoesNotDisplayText("Track time");
|
||||
verifyDisplaysText("Wake up early");
|
||||
|
||||
longPressCheckmarks("Wake up early", 1);
|
||||
verifyDoesNotDisplayText("Wake up early");
|
||||
|
||||
clickMenu(TOGGLE_COMPLETED);
|
||||
verifyDisplaysText("Track time");
|
||||
verifyDisplaysText("Wake up early");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHideNotesCard() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
clickText(EMPTY_DESCRIPTION_HABIT_NAME);
|
||||
verifyShowsScreen(SHOW_HABIT, false);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
import androidx.test.runner.*;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.*;
|
||||
|
||||
import static org.isoron.uhabits.acceptance.steps.CommonSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class LinksTest extends BaseUserInterfaceTest
|
||||
{
|
||||
@Test
|
||||
public void shouldLinkToSourceCode() throws Exception
|
||||
{
|
||||
launchApp();
|
||||
clickMenu(ABOUT);
|
||||
clickText("View source code at GitHub");
|
||||
verifyOpensWebsite("https://github.com/iSoron/uhabits");
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void shouldLinkToTranslationWebsite() throws Exception
|
||||
// {
|
||||
// launchApp();
|
||||
// clickMenu(ABOUT);
|
||||
// clickText("Help translate this app");
|
||||
// verifyOpensWebsite("translate.loophabits.org");
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void shouldLinkToHelp() throws Exception {
|
||||
launchApp();
|
||||
clickMenu(HELP);
|
||||
verifyOpensWebsite("loophabits.org/faq.html");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLinkToHelpFromSettings() throws Exception {
|
||||
launchApp();
|
||||
clickMenu(SETTINGS);
|
||||
clickText("Help & FAQ");
|
||||
verifyOpensWebsite("loophabits.org/faq.html");
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance;
|
||||
|
||||
import androidx.test.filters.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
import org.junit.*;
|
||||
|
||||
import static org.isoron.uhabits.acceptance.steps.CommonSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.WidgetSteps.*;
|
||||
import static org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText;
|
||||
|
||||
@LargeTest
|
||||
public class WidgetTest extends BaseUserInterfaceTest
|
||||
{
|
||||
@Test
|
||||
public void shouldCreateAndToggleCheckmarkWidget() throws Exception
|
||||
{
|
||||
dragCheckmarkWidgetToHomeScreen();
|
||||
clickText("Wake up early");
|
||||
verifyCheckmarkWidgetIsShown();
|
||||
clickCheckmarkWidget();
|
||||
|
||||
launchApp();
|
||||
clickText("Wake up early");
|
||||
verifyDisplaysText("5%");
|
||||
|
||||
pressHome();
|
||||
clickCheckmarkWidget();
|
||||
clickCheckmarkWidget();
|
||||
|
||||
launchApp();
|
||||
clickText("Wake up early");
|
||||
verifyDisplaysText("0%");
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance.steps;
|
||||
|
||||
import android.view.*;
|
||||
|
||||
import androidx.annotation.*;
|
||||
import androidx.recyclerview.widget.*;
|
||||
import androidx.test.espresso.*;
|
||||
import androidx.test.espresso.contrib.*;
|
||||
import androidx.test.uiautomator.*;
|
||||
|
||||
import org.hamcrest.*;
|
||||
import org.isoron.uhabits.*;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.activities.habits.list.*;
|
||||
|
||||
import static android.os.Build.VERSION.*;
|
||||
import static androidx.test.espresso.Espresso.*;
|
||||
import static androidx.test.espresso.action.ViewActions.*;
|
||||
import static androidx.test.espresso.assertion.PositionAssertions.*;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.*;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class CommonSteps extends BaseUserInterfaceTest
|
||||
{
|
||||
public static void clickOK()
|
||||
{
|
||||
clickText("OK");
|
||||
}
|
||||
|
||||
public static void pressBack()
|
||||
{
|
||||
device.pressBack();
|
||||
}
|
||||
|
||||
public static void clickText(String text)
|
||||
{
|
||||
scrollToText(text);
|
||||
onView(withText(text)).perform(click());
|
||||
}
|
||||
|
||||
public static void clickText(@StringRes int id)
|
||||
{
|
||||
onView(withText(id)).perform(click());
|
||||
}
|
||||
|
||||
public static void launchApp()
|
||||
{
|
||||
startActivity(ListHabitsActivity.class);
|
||||
assertTrue(
|
||||
device.wait(Until.hasObject(By.pkg("org.isoron.uhabits")), 5000));
|
||||
device.waitForIdle();
|
||||
}
|
||||
|
||||
public static void longClickText(String text)
|
||||
{
|
||||
scrollToText(text);
|
||||
onView(withText(text)).perform(longClick());
|
||||
}
|
||||
|
||||
public static void pressHome()
|
||||
{
|
||||
device.pressHome();
|
||||
device.waitForIdle();
|
||||
}
|
||||
|
||||
public static void scrollToText(String text)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (device
|
||||
.findObject(new UiSelector().className(RecyclerView.class))
|
||||
.exists())
|
||||
{
|
||||
onView(instanceOf(RecyclerView.class)).perform(
|
||||
RecyclerViewActions.scrollTo(
|
||||
hasDescendant(withText(text))));
|
||||
}
|
||||
else
|
||||
{
|
||||
onView(withText(text)).perform(scrollTo());
|
||||
}
|
||||
}
|
||||
catch (PerformException e)
|
||||
{
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static void verifyDisplayGraphs()
|
||||
{
|
||||
verifyDisplaysView("HistoryCard");
|
||||
verifyDisplaysView("ScoreCard");
|
||||
}
|
||||
|
||||
public static void verifyDisplaysText(String text)
|
||||
{
|
||||
scrollToText(text);
|
||||
onView(withText(text)).check(matches(isEnabled()));
|
||||
}
|
||||
|
||||
public static void verifyDisplaysTextInSequence(String... text)
|
||||
{
|
||||
verifyDisplaysText(text[0]);
|
||||
for(int i = 1; i < text.length; i++) {
|
||||
verifyDisplaysText(text[i]);
|
||||
onView(withText(text[i])).check(isCompletelyBelow(withText(text[i-1])));
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyDisplaysView(String className)
|
||||
{
|
||||
onView(withClassName(endsWith(className))).check(matches(isEnabled()));
|
||||
}
|
||||
|
||||
public static void verifyDoesNotDisplayText(String text)
|
||||
{
|
||||
onView(withText(text)).check(doesNotExist());
|
||||
}
|
||||
|
||||
public static void verifyOpensWebsite(String url) throws Exception
|
||||
{
|
||||
String browser_pkg = "org.chromium.webview_shell";
|
||||
if(SDK_INT <= 23) {
|
||||
browser_pkg = "com.android.browser";
|
||||
}
|
||||
assertTrue(device.wait(Until.hasObject(By.pkg(browser_pkg)), 5000));
|
||||
device.waitForIdle();
|
||||
assertTrue(device.findObject(new UiSelector().textContains(url)).exists());
|
||||
}
|
||||
|
||||
public enum Screen
|
||||
{
|
||||
LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE
|
||||
}
|
||||
|
||||
public static void verifyShowsScreen(Screen screen) {
|
||||
verifyShowsScreen(screen, true);
|
||||
}
|
||||
|
||||
public static void verifyShowsScreen(Screen screen, boolean notesCardVisibleExpected)
|
||||
{
|
||||
switch(screen)
|
||||
{
|
||||
case LIST_HABITS:
|
||||
onView(withClassName(endsWith("ListHabitsRootView")))
|
||||
.check(matches(isDisplayed()));
|
||||
break;
|
||||
|
||||
case SHOW_HABIT:
|
||||
Matcher<View> noteCardViewMatcher = notesCardVisibleExpected ? isDisplayed() :
|
||||
withEffectiveVisibility(Visibility.GONE);
|
||||
onView(withId(R.id.subtitleCard)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.notesCard)).check(matches(noteCardViewMatcher));
|
||||
break;
|
||||
|
||||
case EDIT_HABIT:
|
||||
onView(withId(R.id.questionInput)).check(matches(isDisplayed()));
|
||||
break;
|
||||
|
||||
case SELECT_HABIT_TYPE:
|
||||
onView(withText(R.string.yes_or_no_example)).check(matches(isDisplayed()));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.acceptance.steps;
|
||||
|
||||
import androidx.test.uiautomator.*;
|
||||
|
||||
import org.isoron.uhabits.*;
|
||||
|
||||
import static androidx.test.espresso.Espresso.*;
|
||||
import static androidx.test.espresso.action.ViewActions.*;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||
import static org.isoron.uhabits.BaseUserInterfaceTest.*;
|
||||
|
||||
public class EditHabitSteps
|
||||
{
|
||||
public static void clickSave()
|
||||
{
|
||||
onView(withId(R.id.buttonSave)).perform(click());
|
||||
}
|
||||
|
||||
public static void pickFrequency()
|
||||
{
|
||||
onView(withId(R.id.boolean_frequency_picker)).perform(click());
|
||||
onView(withText("SAVE")).perform(click());
|
||||
}
|
||||
|
||||
public static void pickColor(int color)
|
||||
{
|
||||
onView(withId(R.id.colorButton)).perform(click());
|
||||
device.findObject(By.descStartsWith(String.format("Color %d", color))).click();
|
||||
}
|
||||
|
||||
public static void typeName(String name)
|
||||
{
|
||||
typeTextWithId(R.id.nameInput, name);
|
||||
}
|
||||
|
||||
public static void typeQuestion(String name)
|
||||
{
|
||||
typeTextWithId(R.id.questionInput, name);
|
||||
}
|
||||
|
||||
public static void typeDescription(String description)
|
||||
{
|
||||
typeTextWithId(R.id.notesInput, description);
|
||||
}
|
||||
|
||||
public static void setReminder()
|
||||
{
|
||||
onView(withId(R.id.reminderTimePicker)).perform(click());
|
||||
onView(withId(R.id.done_button)).perform(click());
|
||||
}
|
||||
|
||||
public static void clickReminderDays()
|
||||
{
|
||||
onView(withId(R.id.reminderDatePicker)).perform(click());
|
||||
}
|
||||
|
||||
public static void unselectAllDays()
|
||||
{
|
||||
onView(withText("Saturday")).perform(click());
|
||||
onView(withText("Sunday")).perform(click());
|
||||
onView(withText("Monday")).perform(click());
|
||||
onView(withText("Tuesday")).perform(click());
|
||||
onView(withText("Wednesday")).perform(click());
|
||||
onView(withText("Thursday")).perform(click());
|
||||
onView(withText("Friday")).perform(click());
|
||||
}
|
||||
|
||||
private static void typeTextWithId(int id, String name)
|
||||
{
|
||||
onView(withId(id)).perform(clearText(), typeText(name), closeSoftKeyboard());
|
||||
}
|
||||
}
|
||||