mirror of https://github.com/tasks/tasks
Compare commits
449 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fbfcbdc555 | 2 weeks ago |
|
|
51e347f22b | 2 weeks ago |
|
|
d0c28baf7b | 2 weeks ago |
|
|
3d4d44849e | 3 weeks ago |
|
|
dfa41c515a | 3 weeks ago |
|
|
a539b3a3e4 | 3 weeks ago |
|
|
8d6de19b2a | 3 weeks ago |
|
|
2a6e1638c9 | 4 weeks ago |
|
|
9190930745 | 4 weeks ago |
|
|
40961dad87 | 4 weeks ago |
|
|
9fbe27345d | 4 weeks ago |
|
|
5f67e0ea3a | 4 weeks ago |
|
|
d51171b17e | 4 weeks ago |
|
|
b657773a2d | 4 weeks ago |
|
|
d84effc447 | 4 weeks ago |
|
|
21db540614 | 4 weeks ago |
|
|
8e9f27c46e | 4 weeks ago |
|
|
94ad2a381e | 4 weeks ago |
|
|
747928c8c7 | 4 weeks ago |
|
|
9a28f1062b | 4 weeks ago |
|
|
ffe749bf0c | 4 weeks ago |
|
|
b0ae0129ae | 4 weeks ago |
|
|
6715369d93 | 4 weeks ago |
|
|
4efb678699 | 4 weeks ago |
|
|
581b789a0b | 4 weeks ago |
|
|
15d8b3aa59 | 4 weeks ago |
|
|
dcd5d8c094 | 4 weeks ago |
|
|
f3253e6188 | 4 weeks ago |
|
|
34b0c62ef8 | 4 weeks ago |
|
|
3754196714 | 4 weeks ago |
|
|
b94a91efbe | 4 weeks ago |
|
|
2c6066c378 | 4 weeks ago |
|
|
03e15a8c35 | 4 weeks ago |
|
|
e63add73bc | 4 weeks ago |
|
|
bf676bcea7 | 4 weeks ago |
|
|
6de8fe2fa0 | 4 weeks ago |
|
|
5907f27172 | 4 weeks ago |
|
|
38119d4560 | 4 weeks ago |
|
|
52848a5308 | 4 weeks ago |
|
|
4c492120b3 | 4 weeks ago |
|
|
20e995b19b | 4 weeks ago |
|
|
2b63e33de2 | 4 weeks ago |
|
|
5980bd497d | 2 months ago |
|
|
e16b5cd6cd | 2 months ago |
|
|
3d9945c798 | 2 months ago |
|
|
5c9eb1c35f | 2 months ago |
|
|
6b594a3213 | 2 months ago |
|
|
9e0e01f89b | 2 months ago |
|
|
932b8b0540 | 3 months ago |
|
|
152a9684e5 | 3 months ago |
|
|
88c817b770 | 3 months ago |
|
|
a368960073 | 3 months ago |
|
|
4f1cc5ab8e | 3 months ago |
|
|
17f54b6d32 | 3 months ago |
|
|
6a44bed0e8 | 3 months ago |
|
|
7fa90396b3 | 3 months ago |
|
|
d1df39d12c | 3 months ago |
|
|
d9ddd45f13 | 3 months ago |
|
|
27b21118eb | 3 months ago |
|
|
f725365f87 | 3 months ago |
|
|
7377e4672d | 3 months ago |
|
|
8c90b1ec87 | 3 months ago |
|
|
5b50f45a5b | 3 months ago |
|
|
882338f554 | 3 months ago |
|
|
930e980550 | 3 months ago |
|
|
fb243c7aaf | 3 months ago |
|
|
c3842fd2f7 | 3 months ago |
|
|
81ecb322e9 | 3 months ago |
|
|
1b386458b8 | 3 months ago |
|
|
7dca092831 | 3 months ago |
|
|
2749b029c5 | 3 months ago |
|
|
0cb19221c4 | 3 months ago |
|
|
d900f72a5c | 3 months ago |
|
|
0d8979b72c | 3 months ago |
|
|
703322f510 | 3 months ago |
|
|
192351a4b8 | 3 months ago |
|
|
844a3a0ff8 | 3 months ago |
|
|
e6bbc8d361 | 3 months ago |
|
|
04ab41f622 | 3 months ago |
|
|
4946b0ca06 | 3 months ago |
|
|
2c29194ff2 | 3 months ago |
|
|
1aaaad86da | 3 months ago |
|
|
68601873fd | 3 months ago |
|
|
c9721790ce | 3 months ago |
|
|
6f16a29fd7 | 3 months ago |
|
|
a501b81bfc | 3 months ago |
|
|
fded7fbdd5 | 3 months ago |
|
|
3ff4a2339b | 3 months ago |
|
|
e400594e5b | 3 months ago |
|
|
8bbbc1dcac | 3 months ago |
|
|
38d27b262a | 3 months ago |
|
|
b61842646b | 3 months ago |
|
|
29cbb33a42 | 3 months ago |
|
|
2418b664e9 | 3 months ago |
|
|
0e3803c28d | 3 months ago |
|
|
c0bb7b306a | 3 months ago |
|
|
903412fdea | 3 months ago |
|
|
12b1127e6b | 3 months ago |
|
|
c3ce7a43fb | 3 months ago |
|
|
a391dff9bc | 3 months ago |
|
|
28e12110fa | 3 months ago |
|
|
0bba2c4a63 | 3 months ago |
|
|
700421c5ce | 3 months ago |
|
|
d4a742b136 | 3 months ago |
|
|
585967c601 | 3 months ago |
|
|
d279f7c42e | 3 months ago |
|
|
5cfbe9c8cb | 3 months ago |
|
|
f591b1846c | 3 months ago |
|
|
3401a59716 | 3 months ago |
|
|
d0328b378a | 3 months ago |
|
|
f9d859a33e | 3 months ago |
|
|
5a1560e513 | 3 months ago |
|
|
655cdc1a9d | 3 months ago |
|
|
f2235e6aa6 | 3 months ago |
|
|
09baafb47f | 3 months ago |
|
|
2d0cfaa04d | 3 months ago |
|
|
e60d516fcf | 3 months ago |
|
|
9ba82c3a01 | 3 months ago |
|
|
b7abdfe2ea | 3 months ago |
|
|
3da6f67ace | 4 months ago |
|
|
eec3ae447a | 4 months ago |
|
|
7a9a27eae0 | 4 months ago |
|
|
05b5f1470a | 4 months ago |
|
|
2a94af70fd | 4 months ago |
|
|
464903bf4d | 4 months ago |
|
|
0542f24c29 | 4 months ago |
|
|
0fe834b46c | 4 months ago |
|
|
997810af4c | 4 months ago |
|
|
6829f3f690 | 4 months ago |
|
|
cec5c1e4b8 | 4 months ago |
|
|
d3a12b039a | 4 months ago |
|
|
67dcc1db38 | 4 months ago |
|
|
83ae176288 | 4 months ago |
|
|
5e535b6d46 | 4 months ago |
|
|
5c124047e8 | 4 months ago |
|
|
8a332c8b2a | 4 months ago |
|
|
9d6a925fca | 4 months ago |
|
|
8cc28aa88b | 4 months ago |
|
|
99ea6cb0eb | 4 months ago |
|
|
a698236f4d | 4 months ago |
|
|
ad616472b3 | 4 months ago |
|
|
d5cda9e84b | 4 months ago |
|
|
9808ca1745 | 4 months ago |
|
|
0b76caa9a8 | 4 months ago |
|
|
07ac9f9ead | 4 months ago |
|
|
977edf4d8d | 4 months ago |
|
|
071d670c6d | 4 months ago |
|
|
eb89cc689a | 4 months ago |
|
|
a5c73ccc24 | 4 months ago |
|
|
51884d46f2 | 4 months ago |
|
|
101d7f2357 | 4 months ago |
|
|
75563b6a61 | 4 months ago |
|
|
3028d492b2 | 4 months ago |
|
|
ab2fc34e98 | 4 months ago |
|
|
852ac708b5 | 4 months ago |
|
|
092f357719 | 4 months ago |
|
|
ad1ace8fbf | 4 months ago |
|
|
204f49fc25 | 4 months ago |
|
|
9ef95291c8 | 4 months ago |
|
|
3e034ab91f | 4 months ago |
|
|
6f89ac3b93 | 4 months ago |
|
|
7d2ebf9cdf | 4 months ago |
|
|
16011b1963 | 4 months ago |
|
|
2f6348c53d | 4 months ago |
|
|
566c22c17e | 4 months ago |
|
|
2c33be700a | 4 months ago |
|
|
7a24f43387 | 4 months ago |
|
|
370ac149d3 | 4 months ago |
|
|
4c851ce7f3 | 4 months ago |
|
|
7c78854663 | 4 months ago |
|
|
d05730399d | 4 months ago |
|
|
c8f564d2d5 | 4 months ago |
|
|
65db4ab926 | 4 months ago |
|
|
1476e7fb27 | 4 months ago |
|
|
2ee0939564 | 4 months ago |
|
|
4c530a5de3 | 4 months ago |
|
|
fcd62c6801 | 4 months ago |
|
|
aedd29982a | 4 months ago |
|
|
3a37d6481e | 4 months ago |
|
|
c5f8583146 | 4 months ago |
|
|
9d96bed5b3 | 4 months ago |
|
|
2f268c8c70 | 4 months ago |
|
|
130a29d7e3 | 4 months ago |
|
|
dcb69394be | 4 months ago |
|
|
2cf3438e07 | 4 months ago |
|
|
06e9da41d6 | 4 months ago |
|
|
7b34e33c0e | 4 months ago |
|
|
be51651779 | 4 months ago |
|
|
627b05a575 | 4 months ago |
|
|
8207f30c5f | 5 months ago |
|
|
6811677d21 | 5 months ago |
|
|
9d88c5b3a0 | 5 months ago |
|
|
1f24a371fb | 5 months ago |
|
|
877a2cd6a5 | 5 months ago |
|
|
299b5b4d21 | 5 months ago |
|
|
e6320d42a7 | 5 months ago |
|
|
84c36a1a90 | 5 months ago |
|
|
73c0e38991 | 5 months ago |
|
|
773e822f14 | 5 months ago |
|
|
6f167b5ae0 | 5 months ago |
|
|
ba0cd26abc | 5 months ago |
|
|
3450db4006 | 5 months ago |
|
|
7c2cf38788 | 5 months ago |
|
|
e576a48eba | 5 months ago |
|
|
803593a3a7 | 5 months ago |
|
|
c4fc7fbadb | 5 months ago |
|
|
75d53fb8ac | 5 months ago |
|
|
769802c10a | 5 months ago |
|
|
14ff0086fa | 5 months ago |
|
|
761d4afeef | 5 months ago |
|
|
c7336589cd | 5 months ago |
|
|
e30c583d5a | 5 months ago |
|
|
38527aef0a | 5 months ago |
|
|
c9cdc4d50f | 5 months ago |
|
|
80753f607c | 5 months ago |
|
|
32cb067ffd | 5 months ago |
|
|
e93d0735d4 | 5 months ago |
|
|
103e7eaa60 | 5 months ago |
|
|
a490307251 | 5 months ago |
|
|
976df68671 | 5 months ago |
|
|
384f6e4604 | 5 months ago |
|
|
b68439b0e7 | 5 months ago |
|
|
3611593307 | 5 months ago |
|
|
c7a7384cf5 | 5 months ago |
|
|
f89789dd10 | 5 months ago |
|
|
91da4bc661 | 5 months ago |
|
|
30abeba683 | 5 months ago |
|
|
8f567a153a | 5 months ago |
|
|
2fbffa20cc | 5 months ago |
|
|
a071b05a71 | 5 months ago |
|
|
63dbb48d96 | 5 months ago |
|
|
e2c65c06a1 | 5 months ago |
|
|
03b1d78feb | 5 months ago |
|
|
fe72301c55 | 5 months ago |
|
|
ee40b72b02 | 5 months ago |
|
|
6181351db7 | 5 months ago |
|
|
8263ab2935 | 5 months ago |
|
|
38dbbe379b | 5 months ago |
|
|
58d5eea978 | 5 months ago |
|
|
f902ff38b0 | 5 months ago |
|
|
7c970eec95 | 5 months ago |
|
|
9ec448aedd | 5 months ago |
|
|
2fb1bb4873 | 5 months ago |
|
|
c64e581fd4 | 5 months ago |
|
|
36ec47e9bd | 5 months ago |
|
|
2942554ec8 | 5 months ago |
|
|
b6b624ce5b | 5 months ago |
|
|
1267c803c6 | 5 months ago |
|
|
7a17943142 | 5 months ago |
|
|
d73b8496cb | 5 months ago |
|
|
eda6eeaf62 | 5 months ago |
|
|
6d89f2cb02 | 5 months ago |
|
|
273fbf9153 | 5 months ago |
|
|
2b1ad31f76 | 5 months ago |
|
|
fdd1fc4989 | 5 months ago |
|
|
ff6a8ae0f1 | 5 months ago |
|
|
d1da6dc970 | 5 months ago |
|
|
a3dac4a397 | 5 months ago |
|
|
30060d8faf | 5 months ago |
|
|
a299363fe8 | 6 months ago |
|
|
36b20f47fd | 6 months ago |
|
|
5b1aff00df | 6 months ago |
|
|
f53aec3e8c | 6 months ago |
|
|
079d7867f1 | 6 months ago |
|
|
8b99e8feb2 | 6 months ago |
|
|
1a9b371bda | 6 months ago |
|
|
dfc311ff31 | 6 months ago |
|
|
cbcb812150 | 6 months ago |
|
|
70793f2433 | 6 months ago |
|
|
a198846902 | 6 months ago |
|
|
86a3b2b426 | 6 months ago |
|
|
bbac4da7d0 | 6 months ago |
|
|
f4e0d519d7 | 6 months ago |
|
|
9a584c851b | 6 months ago |
|
|
704edaa0ab | 6 months ago |
|
|
71833adf21 | 6 months ago |
|
|
4aad9bf00e | 6 months ago |
|
|
d4b1a0dd09 | 6 months ago |
|
|
0f1508f59a | 6 months ago |
|
|
266fe1281e | 6 months ago |
|
|
7e99762814 | 6 months ago |
|
|
56ec24d2f9 | 6 months ago |
|
|
fad3ab6ce3 | 6 months ago |
|
|
522bd6e304 | 6 months ago |
|
|
119572971d | 6 months ago |
|
|
48270d6f2c | 6 months ago |
|
|
44d15556c6 | 6 months ago |
|
|
33611d12bd | 6 months ago |
|
|
caa5916ad7 | 6 months ago |
|
|
821e8e0ee4 | 6 months ago |
|
|
fe4bd73d62 | 6 months ago |
|
|
b0493fdd7d | 6 months ago |
|
|
f330daa764 | 6 months ago |
|
|
017dd17021 | 6 months ago |
|
|
aa535981c3 | 6 months ago |
|
|
6f39614b5b | 6 months ago |
|
|
d33d87b6cb | 6 months ago |
|
|
a828d510e3 | 6 months ago |
|
|
898f84e3c8 | 6 months ago |
|
|
a1711fa0ea | 6 months ago |
|
|
542ba69870 | 6 months ago |
|
|
01d07cccbd | 6 months ago |
|
|
3e8838bdb6 | 6 months ago |
|
|
ac4c610841 | 6 months ago |
|
|
6bea199a75 | 6 months ago |
|
|
1efb8c8ee0 | 6 months ago |
|
|
d2c23a79de | 6 months ago |
|
|
0091f80945 | 6 months ago |
|
|
5cb770e722 | 6 months ago |
|
|
81759305c5 | 6 months ago |
|
|
1692a98d3d | 6 months ago |
|
|
9174855f2f | 6 months ago |
|
|
9632ea61d2 | 6 months ago |
|
|
e2ca2a2251 | 6 months ago |
|
|
dad35aafd3 | 6 months ago |
|
|
0735cb5e1d | 6 months ago |
|
|
55c8ab6e3a | 6 months ago |
|
|
5c4b345695 | 6 months ago |
|
|
2c6b1644dc | 6 months ago |
|
|
efaa2cf472 | 6 months ago |
|
|
c4e160a21a | 7 months ago |
|
|
7d6c20aec0 | 7 months ago |
|
|
3960b57242 | 7 months ago |
|
|
0e5e6e9b05 | 7 months ago |
|
|
147c9f44d2 | 7 months ago |
|
|
ad7141a1c9 | 7 months ago |
|
|
6a99e75115 | 7 months ago |
|
|
b4f9770344 | 7 months ago |
|
|
f6ffcf8397 | 7 months ago |
|
|
a23c541195 | 7 months ago |
|
|
dea9783ce5 | 7 months ago |
|
|
93e7a16850 | 7 months ago |
|
|
760bd1d58b | 7 months ago |
|
|
e580ced066 | 7 months ago |
|
|
169140cc0b | 7 months ago |
|
|
8f5c41051b | 7 months ago |
|
|
a2098c1876 | 7 months ago |
|
|
d446e009b4 | 7 months ago |
|
|
35161972c1 | 7 months ago |
|
|
6a8a9dec80 | 7 months ago |
|
|
627ada6679 | 7 months ago |
|
|
c996462e32 | 7 months ago |
|
|
cd5a89960d | 7 months ago |
|
|
805b7d23df | 7 months ago |
|
|
a4ca8b28aa | 7 months ago |
|
|
a519a06c3b | 7 months ago |
|
|
c22384d789 | 7 months ago |
|
|
247c286e61 | 7 months ago |
|
|
15c7bc2fc0 | 7 months ago |
|
|
08fa635ce9 | 7 months ago |
|
|
2946133eb2 | 7 months ago |
|
|
e8f6276b5b | 7 months ago |
|
|
83e8044bc5 | 7 months ago |
|
|
892beb83f7 | 7 months ago |
|
|
a623a7bd97 | 7 months ago |
|
|
98ae90a6ad | 7 months ago |
|
|
9b1078dc16 | 7 months ago |
|
|
cb220654cf | 7 months ago |
|
|
65533d3675 | 7 months ago |
|
|
cdb11e631f | 7 months ago |
|
|
1fd0fe232a | 7 months ago |
|
|
ec05fed425 | 7 months ago |
|
|
1d7cc3794a | 7 months ago |
|
|
3d3c16b297 | 7 months ago |
|
|
deefb20481 | 7 months ago |
|
|
83bc9798d6 | 7 months ago |
|
|
3d04f93aae | 7 months ago |
|
|
15aada7f7f | 7 months ago |
|
|
20e1b802cd | 7 months ago |
|
|
bb4db1e63d | 7 months ago |
|
|
6a5e8896b8 | 7 months ago |
|
|
e469a2ea97 | 7 months ago |
|
|
bfb5529b73 | 7 months ago |
|
|
89101a22d4 | 7 months ago |
|
|
221efb9bef | 7 months ago |
|
|
2757821f4e | 7 months ago |
|
|
ceb7da1e2c | 7 months ago |
|
|
d5f737b6ac | 7 months ago |
|
|
ff33989020 | 7 months ago |
|
|
4c01ab2e66 | 7 months ago |
|
|
93674075cb | 7 months ago |
|
|
6fe8175012 | 7 months ago |
|
|
d72b0f352b | 7 months ago |
|
|
6b92cbee44 | 7 months ago |
|
|
de2aca2877 | 7 months ago |
|
|
1616d9903d | 7 months ago |
|
|
5f1ebd528d | 7 months ago |
|
|
6091410bcd | 7 months ago |
|
|
44eac87c9c | 7 months ago |
|
|
ac9cdb7120 | 7 months ago |
|
|
0dea530c50 | 7 months ago |
|
|
267ebfe86e | 7 months ago |
|
|
be3ca88f46 | 7 months ago |
|
|
eaab2196e7 | 7 months ago |
|
|
c3e10bde94 | 7 months ago |
|
|
68d7a02db8 | 7 months ago |
|
|
e48839b314 | 7 months ago |
|
|
0296008f8a | 7 months ago |
|
|
7c918bbb84 | 7 months ago |
|
|
202af2d451 | 7 months ago |
|
|
a779dc331a | 7 months ago |
|
|
8480f03a96 | 7 months ago |
|
|
c18296e7d0 | 7 months ago |
|
|
520fd0cebe | 7 months ago |
|
|
bd555f4003 | 7 months ago |
|
|
b4b6abac19 | 7 months ago |
|
|
e578263149 | 8 months ago |
|
|
b6989ba9a0 | 8 months ago |
|
|
93d4eab92f | 8 months ago |
|
|
8abf1f0342 | 8 months ago |
|
|
d3fed98e64 | 8 months ago |
|
|
e8b8fc0c87 | 8 months ago |
|
|
939bcef641 | 8 months ago |
|
|
d49ee12271 | 8 months ago |
|
|
3a05d410b0 | 8 months ago |
|
|
591998b9fd | 8 months ago |
|
|
5d8b39d8f1 | 8 months ago |
|
|
c445f0dff0 | 8 months ago |
|
|
20e696d65d | 8 months ago |
|
|
bce545944f | 8 months ago |
|
|
72aaf43db5 | 8 months ago |
|
|
966a529a51 | 8 months ago |
|
|
08bf839e95 | 8 months ago |
|
|
286d031fca | 8 months ago |
|
|
428d04eb46 | 8 months ago |
|
|
fc6f09f097 | 8 months ago |
|
|
dafc677374 | 8 months ago |
|
|
00bf17c9ed | 8 months ago |
|
|
f622209e97 | 8 months ago |
|
|
6d03cf6a6a | 8 months ago |
|
|
cd729bb04c | 8 months ago |
|
|
7d32ada1e4 | 8 months ago |
|
|
526107ce1d | 8 months ago |
|
|
858a5475ad | 8 months ago |
|
|
0c9d2d724a | 8 months ago |
|
|
446fdd817f | 8 months ago |
|
|
b833c0cc5d | 8 months ago |
|
|
5499a74dcc | 8 months ago |
|
|
6c3185da50 | 8 months ago |
|
|
6f0a6787fd | 8 months ago |
|
|
848aad76d6 | 8 months ago |
|
|
1ace1bc618 | 8 months ago |
|
|
cda3e91933 | 8 months ago |
|
|
2c1246ea5c | 8 months ago |
|
|
5731ca2579 | 8 months ago |
|
|
b04b16e24e | 8 months ago |
|
|
53e43b58cd | 8 months ago |
|
|
0c7bc22eb9 | 8 months ago |
|
|
35e340c936 | 8 months ago |
@ -1 +1 @@
|
|||||||
3.3.6
|
3.4.7
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "fastlane"
|
gem "fastlane"
|
||||||
|
gem "abbrev"
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.tasks.data
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.tasks.data.dao.CaldavDao
|
||||||
|
import org.tasks.data.entity.CaldavAccount
|
||||||
|
import org.tasks.injection.InjectingTestCase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class CaldavDaoExtensionsTest : InjectingTestCase() {
|
||||||
|
@Inject lateinit var caldavDao: CaldavDao
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getLocalListCreatesAccountIfNeeded() = runBlocking {
|
||||||
|
withTimeout(5000L) {
|
||||||
|
assertTrue(caldavDao.getAccounts().isEmpty())
|
||||||
|
caldavDao.getLocalList()
|
||||||
|
assertTrue(caldavDao.getAccounts(CaldavAccount.TYPE_LOCAL).isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"client_id" : "9d4babd5-e7ba-4286-ba4b-17274495a901",
|
||||||
|
"authorization_user_agent" : "DEFAULT",
|
||||||
|
"redirect_uri" : "msauth://org.tasks/8wnYBRqh5nnQgFzbIXfxXSs41xE%3D",
|
||||||
|
"account_mode" : "MULTIPLE",
|
||||||
|
"authorities" : [
|
||||||
|
{
|
||||||
|
"type": "AAD",
|
||||||
|
"audience": {
|
||||||
|
"type": "AzureADandPersonalMicrosoftAccount",
|
||||||
|
"tenant_id": "common"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logging": {
|
||||||
|
"level": "verbose",
|
||||||
|
"logcat_enabled": true,
|
||||||
|
"pii_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest>
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<application/>
|
<application tools:ignore="MissingApplicationIcon">
|
||||||
|
<activity
|
||||||
|
android:name=".auth.MicrosoftAuthenticationActivity"
|
||||||
|
android:theme="@style/TranslucentDialog"/>
|
||||||
|
<activity
|
||||||
|
android:name="net.openid.appauth.RedirectUriReceiverActivity"
|
||||||
|
android:exported="true"
|
||||||
|
tools:node="merge">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<data
|
||||||
|
android:host="${applicationId}"
|
||||||
|
android:scheme="msauth" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package org.tasks.auth
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import net.openid.appauth.AuthorizationServiceConfiguration
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
data class IdentityProvider(
|
||||||
|
val name: String,
|
||||||
|
val discoveryEndpoint: Uri,
|
||||||
|
val clientId: String,
|
||||||
|
val redirectUri: Uri,
|
||||||
|
val scope: String
|
||||||
|
) {
|
||||||
|
suspend fun retrieveConfig(): AuthorizationServiceConfiguration {
|
||||||
|
return suspendCoroutine { cont ->
|
||||||
|
AuthorizationServiceConfiguration.fetchFromUrl(discoveryEndpoint) { serviceConfiguration, ex ->
|
||||||
|
cont.resumeWith(
|
||||||
|
when {
|
||||||
|
ex != null -> Result.failure(ex)
|
||||||
|
serviceConfiguration != null -> Result.success(serviceConfiguration)
|
||||||
|
else -> Result.failure(IllegalStateException())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MICROSOFT = IdentityProvider(
|
||||||
|
"Microsoft",
|
||||||
|
"https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration".toUri(),
|
||||||
|
"9d4babd5-e7ba-4286-ba4b-17274495a901",
|
||||||
|
"msauth://org.tasks/8wnYBRqh5nnQgFzbIXfxXSs41xE%3D".toUri(),
|
||||||
|
"user.read Tasks.ReadWrite openid offline_access email"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,24 +1,14 @@
|
|||||||
package org.tasks.sync.microsoft
|
package org.tasks.sync.microsoft
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import net.openid.appauth.*
|
import net.openid.appauth.AuthState
|
||||||
import org.tasks.auth.IdentityProvider
|
import net.openid.appauth.AuthorizationException
|
||||||
|
import net.openid.appauth.AuthorizationResponse
|
||||||
|
import net.openid.appauth.AuthorizationService
|
||||||
|
import net.openid.appauth.TokenRequest
|
||||||
|
import net.openid.appauth.TokenResponse
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
suspend fun IdentityProvider.retrieveConfig(): AuthorizationServiceConfiguration {
|
|
||||||
return suspendCoroutine { cont ->
|
|
||||||
AuthorizationServiceConfiguration.fetchFromUrl(discoveryEndpoint) { serviceConfiguration, ex ->
|
|
||||||
cont.resumeWith(
|
|
||||||
when {
|
|
||||||
ex != null -> Result.failure(ex)
|
|
||||||
serviceConfiguration != null -> Result.success(serviceConfiguration)
|
|
||||||
else -> Result.failure(IllegalStateException())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun Context.requestTokenRefresh(state: AuthState) =
|
suspend fun Context.requestTokenRefresh(state: AuthState) =
|
||||||
requestToken(state.createTokenRefreshRequest())
|
requestToken(state.createTokenRefreshRequest())
|
||||||
|
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package org.tasks.sync.microsoft
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import net.openid.appauth.AuthState
|
||||||
|
import org.tasks.data.entity.CaldavAccount
|
||||||
|
import org.tasks.security.KeyStoreEncryption
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MicrosoftTokenProvider @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val encryption: KeyStoreEncryption,
|
||||||
|
) {
|
||||||
|
suspend fun getToken(account: CaldavAccount): String {
|
||||||
|
val authState = encryption.decrypt(account.password)?.let { AuthState.jsonDeserialize(it) }
|
||||||
|
?: throw RuntimeException("Missing credentials")
|
||||||
|
if (authState.needsTokenRefresh) {
|
||||||
|
val (token, ex) = context.requestTokenRefresh(authState)
|
||||||
|
authState.update(token, ex)
|
||||||
|
if (authState.isAuthorized) {
|
||||||
|
account.password = encryption.encrypt(authState.jsonSerializeString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!authState.isAuthorized) {
|
||||||
|
throw RuntimeException("Needs authentication")
|
||||||
|
}
|
||||||
|
return authState.accessToken!!
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package org.tasks.sync.microsoft
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.microsoft.identity.client.AcquireTokenParameters
|
||||||
|
import com.microsoft.identity.client.AuthenticationCallback
|
||||||
|
import com.microsoft.identity.client.IAuthenticationResult
|
||||||
|
import com.microsoft.identity.client.Prompt
|
||||||
|
import com.microsoft.identity.client.PublicClientApplication
|
||||||
|
import com.microsoft.identity.client.exception.MsalException
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.analytics.Constants
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import org.tasks.data.UUIDHelper
|
||||||
|
import org.tasks.data.dao.CaldavDao
|
||||||
|
import org.tasks.data.entity.CaldavAccount
|
||||||
|
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_MICROSOFT
|
||||||
|
import org.tasks.extensions.Context.toast
|
||||||
|
import org.tasks.jobs.WorkManager
|
||||||
|
import org.tasks.sync.SyncAdapters
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class MicrosoftSignInViewModel @Inject constructor(
|
||||||
|
private val caldavDao: CaldavDao,
|
||||||
|
private val firebase: Firebase,
|
||||||
|
private val syncAdapters: SyncAdapters,
|
||||||
|
private val workManager: WorkManager,
|
||||||
|
) : ViewModel() {
|
||||||
|
fun signIn(activity: Activity) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val app = PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||||
|
activity,
|
||||||
|
R.raw.microsoft_config
|
||||||
|
)
|
||||||
|
|
||||||
|
val parameters = AcquireTokenParameters.Builder()
|
||||||
|
.startAuthorizationFromActivity(activity)
|
||||||
|
.withScopes(scopes)
|
||||||
|
.withPrompt(Prompt.SELECT_ACCOUNT)
|
||||||
|
.withCallback(object : AuthenticationCallback {
|
||||||
|
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||||
|
val email = authenticationResult.account.claims?.get("preferred_username") as? String
|
||||||
|
if (email == null) {
|
||||||
|
Timber.e("No email found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.d("Successfully signed in")
|
||||||
|
viewModelScope.launch {
|
||||||
|
caldavDao
|
||||||
|
.getAccount(TYPE_MICROSOFT, email)
|
||||||
|
?.let {
|
||||||
|
caldavDao.update(
|
||||||
|
it.copy(error = null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
?: caldavDao
|
||||||
|
.insert(
|
||||||
|
CaldavAccount(
|
||||||
|
uuid = UUIDHelper.newUUID(),
|
||||||
|
name = email,
|
||||||
|
username = email,
|
||||||
|
accountType = TYPE_MICROSOFT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.also {
|
||||||
|
firebase.logEvent(
|
||||||
|
R.string.event_sync_add_account,
|
||||||
|
R.string.param_type to Constants.SYNC_TYPE_MICROSOFT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
syncAdapters.sync(true)
|
||||||
|
workManager.updateBackgroundSync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: MsalException?) {
|
||||||
|
Timber.e(exception)
|
||||||
|
activity.toast(exception?.message ?: exception?.javaClass?.simpleName ?: "Sign in failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
Timber.d("onCancel")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
app.acquireToken(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val scopes = listOf("https://graph.microsoft.com/.default")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package org.tasks.sync.microsoft
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.microsoft.identity.client.AcquireTokenSilentParameters
|
||||||
|
import com.microsoft.identity.client.PublicClientApplication
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.data.entity.CaldavAccount
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MicrosoftTokenProvider @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
) {
|
||||||
|
fun getToken(account: CaldavAccount): String {
|
||||||
|
val app = PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||||
|
context,
|
||||||
|
R.raw.microsoft_config
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = try {
|
||||||
|
val msalAccount = app.accounts.firstOrNull { it.username == account.username }
|
||||||
|
?: throw RuntimeException("No matching account found")
|
||||||
|
|
||||||
|
val parameters = AcquireTokenSilentParameters.Builder()
|
||||||
|
.withScopes(MicrosoftSignInViewModel.scopes)
|
||||||
|
.forAccount(msalAccount)
|
||||||
|
.fromAuthority(msalAccount.authority)
|
||||||
|
.forceRefresh(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
app.acquireTokenSilent(parameters)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
throw RuntimeException("Authentication failed: ${e.message}")
|
||||||
|
}
|
||||||
|
return result.accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"client_id" : "9d4babd5-e7ba-4286-ba4b-17274495a901",
|
||||||
|
"authorization_user_agent" : "DEFAULT",
|
||||||
|
"redirect_uri" : "msauth://org.tasks/sEe08kX5nGJi4miFX3VkNXICC%2FY%3D",
|
||||||
|
"account_mode" : "MULTIPLE",
|
||||||
|
"authorities" : [
|
||||||
|
{
|
||||||
|
"type": "AAD",
|
||||||
|
"audience": {
|
||||||
|
"type": "AzureADandPersonalMicrosoftAccount",
|
||||||
|
"tenant_id": "common"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logging": {
|
||||||
|
"level": "info",
|
||||||
|
"logcat_enabled": true,
|
||||||
|
"pii_enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="google_oauth_scheme">com.googleusercontent.apps.363426363175-jdrijf7hql9030klgjcjlpi6k5spviif</string>
|
<string name="google_oauth_scheme">com.googleusercontent.apps.363426363175-jdrijf7hql9030klgjcjlpi6k5spviif</string>
|
||||||
|
<string name="microsoft_oauth_path">/sEe08kX5nGJi4miFX3VkNXICC/Y=</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package org.tasks.auth
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
data class IdentityProvider(
|
|
||||||
val name: String,
|
|
||||||
val discoveryEndpoint: Uri,
|
|
||||||
val clientId: String,
|
|
||||||
val redirectUri: Uri,
|
|
||||||
val scope: String
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
val MICROSOFT = IdentityProvider(
|
|
||||||
"Microsoft",
|
|
||||||
"https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration".toUri(),
|
|
||||||
"9d4babd5-e7ba-4286-ba4b-17274495a901",
|
|
||||||
"msauth://org.tasks/8wnYBRqh5nnQgFzbIXfxXSs41xE%3D".toUri(),
|
|
||||||
"user.read Tasks.ReadWrite openid offline_access email"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
package org.tasks.caldav
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.analytics.Constants
|
||||||
|
import org.tasks.data.UUIDHelper
|
||||||
|
import org.tasks.data.entity.CaldavAccount
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LocalAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolbar.OnMenuItemClickListener {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding.userLayout.visibility = View.GONE
|
||||||
|
binding.passwordLayout.visibility = View.GONE
|
||||||
|
binding.urlLayout.visibility = View.GONE
|
||||||
|
binding.serverSelector.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges() = newName != caldavAccount!!.name
|
||||||
|
|
||||||
|
override fun save() = lifecycleScope.launch {
|
||||||
|
if (newName.isBlank()) {
|
||||||
|
binding.nameLayout.error = getString(R.string.name_cannot_be_empty)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
updateAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun addAccount() {
|
||||||
|
caldavDao.insert(
|
||||||
|
CaldavAccount(
|
||||||
|
name = newName,
|
||||||
|
uuid = UUIDHelper.newUUID(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
firebase.logEvent(
|
||||||
|
R.string.event_sync_add_account,
|
||||||
|
R.string.param_type to Constants.SYNC_TYPE_LOCAL
|
||||||
|
)
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAccount() {
|
||||||
|
caldavAccount!!.name = newName
|
||||||
|
caldavDao.update(caldavAccount!!)
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addAccount(url: String, username: String, password: String) {
|
||||||
|
addAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAccount(url: String, username: String, password: String) {
|
||||||
|
updateAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeAccountPrompt() {
|
||||||
|
val countTasks = caldavAccount?.uuid?.let { caldavDao.countTasks(it) } ?: 0
|
||||||
|
val countString = resources.getQuantityString(R.plurals.task_count, countTasks, countTasks)
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog()
|
||||||
|
.setTitle(
|
||||||
|
R.string.delete_tag_confirmation,
|
||||||
|
caldavAccount?.name?.takeIf { it.isNotBlank() } ?: getString(R.string.local_lists)
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
if (countTasks > 0) {
|
||||||
|
setMessage(R.string.delete_tasks_warning, countString)
|
||||||
|
} else {
|
||||||
|
setMessage(R.string.logout_warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setPositiveButton(R.string.delete) { _, _ -> lifecycleScope.launch { removeAccount() } }
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val newPassword: String? = null
|
||||||
|
|
||||||
|
override val helpUrl = R.string.url_caldav
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.tasks.caldav.property
|
||||||
|
|
||||||
|
import at.bitfire.dav4jvm.Property
|
||||||
|
import at.bitfire.dav4jvm.PropertyFactory
|
||||||
|
import at.bitfire.dav4jvm.XmlUtils
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
data class CalendarIcon(
|
||||||
|
val icon: String,
|
||||||
|
): Property {
|
||||||
|
companion object Companion {
|
||||||
|
@JvmField
|
||||||
|
val NAME = Property.Name(PropertyUtils.NS_TASKS, "x-calendar-icon")
|
||||||
|
}
|
||||||
|
|
||||||
|
object Factory: PropertyFactory {
|
||||||
|
|
||||||
|
override fun getName() = NAME
|
||||||
|
|
||||||
|
override fun create(parser: XmlPullParser): CalendarIcon? {
|
||||||
|
XmlUtils.readText(parser)?.takeIf { it.isNotBlank() }?.let {
|
||||||
|
try {
|
||||||
|
return CalendarIcon(it)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Timber.e(e, "Couldn't parse icon: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,6 @@
|
|||||||
package org.tasks.caldav.property
|
package org.tasks.caldav.property
|
||||||
|
|
||||||
import at.bitfire.dav4jvm.PropertyFactory
|
|
||||||
import at.bitfire.dav4jvm.PropertyRegistry
|
|
||||||
|
|
||||||
object PropertyUtils {
|
object PropertyUtils {
|
||||||
|
const val NS_TASKS = "http://org.tasks/ns/"
|
||||||
const val NS_OWNCLOUD = "http://owncloud.org/ns"
|
const val NS_OWNCLOUD = "http://owncloud.org/ns"
|
||||||
|
}
|
||||||
fun PropertyRegistry.register(vararg factories: PropertyFactory) = register(factories.toList())
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
package org.tasks.compose
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object HomeDestination
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AddAccountDestination(val showImport: Boolean)
|
||||||
@ -0,0 +1,348 @@
|
|||||||
|
package org.tasks.compose.accounts
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Backup
|
||||||
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewFontScale
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.sync.AddAccountDialog.Platform
|
||||||
|
import org.tasks.themes.TasksTheme
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun AddAccountScreen(
|
||||||
|
gettingStarted: Boolean,
|
||||||
|
hasTasksAccount: Boolean,
|
||||||
|
hasPro: Boolean,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
signIn: (Platform) -> Unit,
|
||||||
|
openUrl: (Platform) -> Unit,
|
||||||
|
onImportBackup: () -> Unit,
|
||||||
|
) {
|
||||||
|
BackHandler {
|
||||||
|
if (!gettingStarted) {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(),
|
||||||
|
navigationIcon = {
|
||||||
|
if (!gettingStarted) {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.back),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = if (gettingStarted) {
|
||||||
|
stringResource(R.string.sign_in)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.add_account)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
maxItemsInEachRow = 5
|
||||||
|
) {
|
||||||
|
if (gettingStarted) {
|
||||||
|
ActionCard(
|
||||||
|
title = R.string.backup_BAc_import,
|
||||||
|
icon = Icons.Outlined.Backup,
|
||||||
|
onClick = onImportBackup,
|
||||||
|
isOutlined = true
|
||||||
|
)
|
||||||
|
|
||||||
|
ActionCard(
|
||||||
|
title = R.string.continue_without_sync,
|
||||||
|
icon = Icons.Outlined.CloudOff,
|
||||||
|
onClick = { signIn(Platform.LOCAL) },
|
||||||
|
isOutlined = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!hasTasksAccount) {
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.tasks_org,
|
||||||
|
cost = R.string.cost_more_money,
|
||||||
|
icon = R.drawable.ic_round_icon,
|
||||||
|
onClick = { signIn(Platform.TASKS_ORG) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.microsoft,
|
||||||
|
cost = if (hasPro) null else R.string.cost_free,
|
||||||
|
icon = R.drawable.ic_microsoft_tasks,
|
||||||
|
onClick = { signIn(Platform.MICROSOFT) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.gtasks_GPr_header,
|
||||||
|
cost = if (hasPro) null else R.string.cost_free,
|
||||||
|
icon = R.drawable.ic_google,
|
||||||
|
onClick = { signIn(Platform.GOOGLE_TASKS) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.davx5,
|
||||||
|
cost = if (hasPro) null else R.string.cost_money,
|
||||||
|
icon = R.drawable.ic_davx5_icon_green_bg,
|
||||||
|
onClick = { openUrl(Platform.DAVX5) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.caldav,
|
||||||
|
cost = if (hasPro) null else R.string.cost_money,
|
||||||
|
icon = R.drawable.ic_webdav_logo,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = .8f),
|
||||||
|
onClick = { signIn(Platform.CALDAV) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.etesync,
|
||||||
|
cost = if (hasPro) null else R.string.cost_money,
|
||||||
|
icon = R.drawable.ic_etesync,
|
||||||
|
onClick = { signIn(Platform.ETESYNC) }
|
||||||
|
)
|
||||||
|
|
||||||
|
AccountTypeCard(
|
||||||
|
title = R.string.decsync,
|
||||||
|
cost = if (hasPro) null else R.string.cost_money,
|
||||||
|
icon = R.drawable.ic_decsync,
|
||||||
|
onClick = { openUrl(Platform.DECSYNC_CC) }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (gettingStarted) {
|
||||||
|
ActionCard(
|
||||||
|
title = R.string.help_me_choose,
|
||||||
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
|
onClick = { openUrl(Platform.LOCAL) },
|
||||||
|
isOutlined = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccountTypeCard(
|
||||||
|
@StringRes title: Int,
|
||||||
|
@StringRes cost: Int? = null,
|
||||||
|
@DrawableRes icon: Int,
|
||||||
|
tint: Color? = null,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(108.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(12.dp).fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = icon),
|
||||||
|
contentDescription = stringResource(id = title),
|
||||||
|
tint = tint ?: Color.Unspecified,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
append(stringResource(id = title))
|
||||||
|
cost?.let {
|
||||||
|
append("\n")
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontSize = MaterialTheme.typography.labelSmall.fontSize
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append(stringResource(id = it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
minLines = 3,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionCard(
|
||||||
|
@StringRes title: Int,
|
||||||
|
icon: ImageVector,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
isOutlined: Boolean = false
|
||||||
|
) {
|
||||||
|
if (isOutlined) {
|
||||||
|
OutlinedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(108.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = stringResource(id = title),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(40.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = title),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
minLines = 3,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(150.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = stringResource(id = title),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(40.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = title),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@PreviewScreenSizes
|
||||||
|
@PreviewFontScale
|
||||||
|
@Composable
|
||||||
|
fun GettingStartedPreview() {
|
||||||
|
TasksTheme {
|
||||||
|
AddAccountScreen(
|
||||||
|
gettingStarted = true,
|
||||||
|
hasTasksAccount = false,
|
||||||
|
hasPro = false,
|
||||||
|
onBack = {},
|
||||||
|
signIn = {},
|
||||||
|
openUrl = {},
|
||||||
|
onImportBackup = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
fun AddAccountPreview() {
|
||||||
|
TasksTheme {
|
||||||
|
AddAccountScreen(
|
||||||
|
gettingStarted = false,
|
||||||
|
hasTasksAccount = false,
|
||||||
|
hasPro = false,
|
||||||
|
onBack = {},
|
||||||
|
signIn = {},
|
||||||
|
openUrl = {},
|
||||||
|
onImportBackup = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.tasks.compose.accounts
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.data.dao.CaldavDao
|
||||||
|
import org.tasks.data.newLocalAccount
|
||||||
|
import org.tasks.extensions.Context.openUri
|
||||||
|
import org.tasks.sync.AddAccountDialog
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AddAccountViewModel @Inject constructor(
|
||||||
|
private val caldavDao: CaldavDao,
|
||||||
|
) : ViewModel() {
|
||||||
|
fun createLocalAccount() = viewModelScope.launch {
|
||||||
|
caldavDao.newLocalAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openUrl(context: Context, platform: AddAccountDialog.Platform) {
|
||||||
|
val url = when (platform) {
|
||||||
|
AddAccountDialog.Platform.DAVX5 -> R.string.url_davx5
|
||||||
|
AddAccountDialog.Platform.DECSYNC_CC -> R.string.url_decsync
|
||||||
|
AddAccountDialog.Platform.LOCAL -> R.string.help_url_sync
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
context.openUri(context.getString(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,316 @@
|
|||||||
|
package org.tasks.compose.home
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.LocalActivity
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.ime
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||||
|
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
|
||||||
|
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
||||||
|
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
|
||||||
|
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.IntentCompat.getParcelableExtra
|
||||||
|
import androidx.fragment.compose.AndroidFragment
|
||||||
|
import androidx.fragment.compose.rememberFragmentState
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.todoroo.astrid.activity.MainActivity.Companion.OPEN_FILTER
|
||||||
|
import com.todoroo.astrid.activity.MainActivityViewModel
|
||||||
|
import com.todoroo.astrid.activity.TaskEditFragment
|
||||||
|
import com.todoroo.astrid.activity.TaskEditFragment.Companion.EXTRA_TASK
|
||||||
|
import com.todoroo.astrid.activity.TaskListFragment
|
||||||
|
import com.todoroo.astrid.activity.TaskListFragment.Companion.EXTRA_FILTER
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.TasksApplication
|
||||||
|
import org.tasks.activities.TagSettingsActivity
|
||||||
|
import org.tasks.billing.PurchaseActivity
|
||||||
|
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity.Companion.EXTRA_CALDAV_ACCOUNT
|
||||||
|
import org.tasks.compose.drawer.DrawerAction
|
||||||
|
import org.tasks.compose.drawer.DrawerItem
|
||||||
|
import org.tasks.compose.drawer.MenuSearchBar
|
||||||
|
import org.tasks.compose.drawer.TaskListDrawer
|
||||||
|
import org.tasks.data.listSettingsClass
|
||||||
|
import org.tasks.extensions.Context.openUri
|
||||||
|
import org.tasks.filters.Filter
|
||||||
|
import org.tasks.filters.FilterProvider
|
||||||
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_LIST
|
||||||
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_PLACE
|
||||||
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_TAGS
|
||||||
|
import org.tasks.filters.NavigationDrawerSubheader
|
||||||
|
import org.tasks.kmp.org.tasks.compose.TouchSlopMultiplier
|
||||||
|
import org.tasks.kmp.org.tasks.compose.rememberImeState
|
||||||
|
import org.tasks.location.LocationPickerActivity
|
||||||
|
import org.tasks.preferences.HelpAndFeedback
|
||||||
|
import org.tasks.preferences.MainPreferences
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun HomeScreen(
|
||||||
|
viewModel: MainActivityViewModel = hiltViewModel(LocalActivity.current as ComponentActivity),
|
||||||
|
state: MainActivityViewModel.State,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
showNewFilterDialog: () -> Unit,
|
||||||
|
navigator: ThreePaneScaffoldNavigator<Any>,
|
||||||
|
) {
|
||||||
|
val currentWindowInsets = WindowInsets.systemBars.asPaddingValues()
|
||||||
|
val windowInsets = remember { mutableStateOf(currentWindowInsets) }
|
||||||
|
val keyboard = LocalSoftwareKeyboardController.current
|
||||||
|
val newList =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
result.data
|
||||||
|
?.let { getParcelableExtra(it, OPEN_FILTER, Filter::class.java) }
|
||||||
|
?.let { viewModel.setFilter(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(currentWindowInsets) {
|
||||||
|
Timber.d("insets: $currentWindowInsets")
|
||||||
|
if (currentWindowInsets.calculateTopPadding() != 0.dp || currentWindowInsets.calculateBottomPadding() != 0.dp) {
|
||||||
|
windowInsets.value = currentWindowInsets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isListVisible =
|
||||||
|
navigator.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
|
||||||
|
val isDetailVisible =
|
||||||
|
navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
|
||||||
|
|
||||||
|
TouchSlopMultiplier {
|
||||||
|
ModalNavigationDrawer(
|
||||||
|
drawerState = drawerState,
|
||||||
|
gesturesEnabled = isListVisible,
|
||||||
|
drawerContent = {
|
||||||
|
ModalDrawerSheet(
|
||||||
|
drawerState = drawerState,
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
TaskListDrawer(
|
||||||
|
arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom,
|
||||||
|
filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems,
|
||||||
|
onClick = {
|
||||||
|
when (it) {
|
||||||
|
is DrawerItem.Filter -> {
|
||||||
|
viewModel.setFilter(it.filter)
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
keyboard?.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is DrawerItem.Header -> {
|
||||||
|
viewModel.toggleCollapsed(it.header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
when (it.header.addIntentRc) {
|
||||||
|
FilterProvider.REQUEST_NEW_FILTER ->
|
||||||
|
showNewFilterDialog()
|
||||||
|
|
||||||
|
REQUEST_NEW_PLACE ->
|
||||||
|
newList.launch(Intent(context, LocationPickerActivity::class.java))
|
||||||
|
|
||||||
|
REQUEST_NEW_TAGS ->
|
||||||
|
newList.launch(Intent(context, TagSettingsActivity::class.java))
|
||||||
|
|
||||||
|
REQUEST_NEW_LIST ->
|
||||||
|
when (it.header.subheaderType) {
|
||||||
|
NavigationDrawerSubheader.SubheaderType.CALDAV,
|
||||||
|
NavigationDrawerSubheader.SubheaderType.TASKS ->
|
||||||
|
viewModel
|
||||||
|
.getAccount(it.header.id.toLong())
|
||||||
|
?.let {
|
||||||
|
newList.launch(
|
||||||
|
Intent(context, it.listSettingsClass())
|
||||||
|
.putExtra(EXTRA_CALDAV_ACCOUNT, it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Timber.e("Unhandled request code: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onErrorClick = {
|
||||||
|
context.startActivity(Intent(context, MainPreferences::class.java))
|
||||||
|
},
|
||||||
|
searchBar = {
|
||||||
|
MenuSearchBar(
|
||||||
|
begForMoney = state.begForMoney,
|
||||||
|
onDrawerAction = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
when (it) {
|
||||||
|
DrawerAction.PURCHASE ->
|
||||||
|
if (TasksApplication.IS_GENERIC)
|
||||||
|
context.openUri(R.string.url_donate)
|
||||||
|
else
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
PurchaseActivity::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
DrawerAction.HELP_AND_FEEDBACK ->
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
HelpAndFeedback::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
query = state.menuQuery,
|
||||||
|
onQueryChange = { viewModel.queryMenu(it) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
SystemBarScrim(
|
||||||
|
modifier = Modifier
|
||||||
|
.windowInsetsTopHeight(WindowInsets.systemBars)
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
SystemBarScrim(
|
||||||
|
modifier = Modifier
|
||||||
|
.windowInsetsBottomHeight(WindowInsets.systemBars)
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
ListDetailPaneScaffold(
|
||||||
|
directive = navigator.scaffoldDirective,
|
||||||
|
value = navigator.scaffoldValue,
|
||||||
|
listPane = {
|
||||||
|
key (state.filter) {
|
||||||
|
val fragment = remember { mutableStateOf<TaskListFragment?>(null) }
|
||||||
|
val keyboardOpen = rememberImeState()
|
||||||
|
AndroidFragment<TaskListFragment>(
|
||||||
|
fragmentState = rememberFragmentState(),
|
||||||
|
arguments = remember(state.filter) {
|
||||||
|
Bundle()
|
||||||
|
.apply { putParcelable(EXTRA_FILTER, state.filter) }
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.imePadding(),
|
||||||
|
) { tlf ->
|
||||||
|
fragment.value = tlf
|
||||||
|
tlf.applyInsets(windowInsets.value)
|
||||||
|
tlf.setNavigationClickListener {
|
||||||
|
scope.launch { drawerState.open() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(fragment, windowInsets, keyboardOpen.value) {
|
||||||
|
fragment.value?.applyInsets(
|
||||||
|
if (keyboardOpen.value) {
|
||||||
|
PaddingValues(
|
||||||
|
top = windowInsets.value.calculateTopPadding(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
windowInsets.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detailPane = {
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
top = windowInsets.value.calculateTopPadding(),
|
||||||
|
start = windowInsets.value.calculateStartPadding(direction),
|
||||||
|
end = windowInsets.value.calculateEndPadding(direction),
|
||||||
|
bottom = if (rememberImeState().value)
|
||||||
|
WindowInsets.ime.asPaddingValues().calculateBottomPadding()
|
||||||
|
else
|
||||||
|
windowInsets.value.calculateBottomPadding()
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (state.task == null) {
|
||||||
|
if (isListVisible && isDetailVisible) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(192.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key(state.task) {
|
||||||
|
AndroidFragment<TaskEditFragment>(
|
||||||
|
fragmentState = rememberFragmentState(),
|
||||||
|
arguments = remember(state.task) {
|
||||||
|
Bundle()
|
||||||
|
.apply { putParcelable(EXTRA_TASK, state.task) }
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
SystemBarScrim(
|
||||||
|
modifier = Modifier
|
||||||
|
.windowInsetsTopHeight(WindowInsets.systemBars)
|
||||||
|
.align(Alignment.TopCenter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue