Compare commits
856 Commits
feature/re
...
feature/ro
Author | SHA1 | Date | |
---|---|---|---|
2948018453 | |||
b7e0b400fb | |||
a8f06f609b | |||
d469299f42 | |||
9182f2ce4e | |||
10e4d0190f | |||
b77310fe92 | |||
919dec4a56 | |||
43b3680774 | |||
bfb5fce809 | |||
1f3731aae7 | |||
52dced43ff | |||
ff80c3c8d5 | |||
34e4d27573 | |||
6522148e63 | |||
252b2ea30a | |||
f493ce44f2 | |||
c4c5069ee5 | |||
423125b5d9 | |||
9e3d29b7d7 | |||
f65becf7c0 | |||
80a61cf6b5 | |||
77056aff94 | |||
65e123d87f | |||
d0b145d031 | |||
98306e223b | |||
c9fe1adb77 | |||
1b95336ad3 | |||
f007fb04b8 | |||
141434e8f8 | |||
b8669d5ed2 | |||
7a08a11b19 | |||
54b1d18812 | |||
3aa30e5f15 | |||
ddf4a81905 | |||
794fd650a4 | |||
9a57a02996 | |||
7e8cd07e1e | |||
d613abf4b4 | |||
06699eaefc | |||
e5082f662c | |||
c8ab53e39c | |||
d424a135a9 | |||
e6409d4c60 | |||
19c7de687e | |||
1918302297 | |||
92e3a02389 | |||
0a54801fcc | |||
228ee52563 | |||
e6c74dc1fe | |||
fe82ad2002 | |||
f66739491a | |||
c5dc9d4a9a | |||
8f858f8119 | |||
6e036c24b8 | |||
5e832e07cd | |||
e9700e04d8 | |||
c19b1f917f | |||
4281b5967a | |||
aa743d8469 | |||
a09850b16c | |||
6cb94dd4d6 | |||
c9931e3ba3 | |||
fc302c1b5a | |||
34ac987494 | |||
24b2387703 | |||
8a0c9ae9b0 | |||
a79227424f | |||
ffe0b9712c | |||
d92c090c30 | |||
1a4157a663 | |||
fa81d1a9c7 | |||
dba4df6836 | |||
4aae1f78d8 | |||
8159a52bd7 | |||
95d83db90c | |||
ac5b0af63e | |||
e80473903e | |||
d08778c674 | |||
0919b9460d | |||
66a018c79e | |||
dcd64de4b8 | |||
a0bd206308 | |||
ba589e7961 | |||
5dc83d64c1 | |||
9a4eb8e9a4 | |||
058e7153a1 | |||
d7b2371854 | |||
b0c939866f | |||
a07f8b615e | |||
12bd85e0a9 | |||
1b82ed5abb | |||
c13ab62187 | |||
ea77686746 | |||
8a5612be3d | |||
d24ce27903 | |||
2099965508 | |||
829e8da8dc | |||
e149ee53de | |||
b73d3b15f8 | |||
61d7f23870 | |||
b5650b2b8f | |||
8777d13d8b | |||
d52613d723 | |||
7ce476f858 | |||
dd07f5c2a6 | |||
7e6e09bc19 | |||
1d11a163af | |||
57bd103de8 | |||
25bc5001f9 | |||
e4c52484b1 | |||
a30da07fd1 | |||
ee27d3e047 | |||
7096094224 | |||
443fb41d18 | |||
94b4351e19 | |||
e90aeff417 | |||
e50dd265d4 | |||
4521ea14ee | |||
535b41d818 | |||
21357a1ec7 | |||
8c872caf78 | |||
62a81a556e | |||
568e8c8bc0 | |||
98a7652403 | |||
78951b9155 | |||
8c86a653b2 | |||
ea0526821e | |||
c503445092 | |||
205af8b122 | |||
3abb7c8de6 | |||
a40510da3b | |||
a6ab4a349d | |||
79a704d240 | |||
e5adf174a8 | |||
f01e796271 | |||
302d23ba96 | |||
03050c3f25 | |||
cbfd2af74b | |||
f3fab0dc08 | |||
4a512d2425 | |||
07f80f43bd | |||
87dec337d8 | |||
b37877746a | |||
b0e5612bdc | |||
25b0cd0e4b | |||
2800d86a57 | |||
01bc0de2c2 | |||
857a4c5a26 | |||
063c35380a | |||
5322251bc0 | |||
c21b9df9a5 | |||
f2a52f0253 | |||
baaf493cb4 | |||
6cbd6d3a33 | |||
72e5aa981a | |||
c0f085cdf8 | |||
10bc2297d4 | |||
8fa5e63b07 | |||
9d0c50907c | |||
e5958983d8 | |||
ab23ec3f35 | |||
a79a6443e7 | |||
9ff24cbf2a | |||
2eee25bbc1 | |||
2a2431e490 | |||
4041e2e8ca | |||
031c4e5746 | |||
b4ea85fc76 | |||
480f14902d | |||
20c8e8d922 | |||
9cdecced57 | |||
60d46538de | |||
223295c2f1 | |||
f789fb275d | |||
a7c12aeb93 | |||
0ca9a5f68b | |||
842345df9b | |||
7d5c31c510 | |||
1ee1c31b9c | |||
e9eada77f9 | |||
93ce0cc5e9 | |||
eefd09d022 | |||
ef597cc67a | |||
5d171e0240 | |||
39070820be | |||
1fdad38b9d | |||
f41c0311fa | |||
a476ac71da | |||
bc2d321a84 | |||
9adeab6bae | |||
0f3a63e366 | |||
d90698fe92 | |||
af0af6e260 | |||
6e71fb565a | |||
6c66ab1568 | |||
77fd7cd33c | |||
0d329f0338 | |||
2f66321c2a | |||
5b102485bc | |||
313055b96d | |||
cbe8236036 | |||
37199da52f | |||
ec5ec3375a | |||
156d88e7e2 | |||
504009499f | |||
11bf00030d | |||
73277c5b08 | |||
f21f4dbe91 | |||
504d7e95fd | |||
62d2443b85 | |||
bc1edcf33d | |||
363f52b10c | |||
498b1f2b06 | |||
92222c269e | |||
ee9440c1cb | |||
fe81145680 | |||
0980a41752 | |||
18a821f3f6 | |||
14a2570ea4 | |||
994ee1d23f | |||
f0e43d31f5 | |||
85e850bcde | |||
6cf24cc43b | |||
c7df433a44 | |||
9378d30601 | |||
58a80ec543 | |||
95ea6db946 | |||
a47af4d915 | |||
0c2d3f36c3 | |||
014d03893a | |||
164c8dab09 | |||
b54ca5a8a0 | |||
707a4712fc | |||
8e76700c8d | |||
3f74c4e933 | |||
41ed4b23d8 | |||
ce9fa15bcb | |||
f9880283e9 | |||
f4b124d29f | |||
fb1f107911 | |||
8e653979fd | |||
a5584d27af | |||
de9a5a3d12 | |||
95d64008aa | |||
19202cfca6 | |||
ca2223f201 | |||
a1ddd73d7d | |||
01e3e71f98 | |||
8fefdc1019 | |||
07309c90e1 | |||
0bdde4d994 | |||
b25098c52d | |||
5e9ecfbcc0 | |||
e1b6f4bd74 | |||
4d79485fee | |||
9224fcabfa | |||
0d433b2620 | |||
6a829caf0f | |||
d643abbb22 | |||
e838794587 | |||
b0ad568df0 | |||
4739aea793 | |||
3960742f38 | |||
2eef0a6162 | |||
466a39d239 | |||
e78b703387 | |||
9df928e709 | |||
a734c699ad | |||
b402bf829b | |||
4d7f1b4fee | |||
38ceb6f52a | |||
419ef7b46f | |||
4ad23f0f37 | |||
0f039fce32 | |||
fef1c7cc45 | |||
7ff2477a4f | |||
7af55a48f6 | |||
ee402fd328 | |||
75c1718252 | |||
f83491fdfc | |||
a9dd06562a | |||
cea8abb9b1 | |||
13a0b809e1 | |||
d28e9862a2 | |||
bf68a6bafc | |||
b14a6224ba | |||
b92cc524b6 | |||
17a4a86ad1 | |||
0a908136b6 | |||
83ceb36a12 | |||
2ef53e2066 | |||
519f49b50d | |||
a550743f2f | |||
40bf3a15cd | |||
4422ebb77b | |||
99271ce5d6 | |||
2c9280dca6 | |||
7187cc23a1 | |||
10353c9871 | |||
f410538e2f | |||
76fc455d93 | |||
22c005d05a | |||
604de7eebc | |||
f18bc9bd00 | |||
f91959ea96 | |||
2063a3e535 | |||
a1c22c9aa6 | |||
d5625b95fe | |||
d205f63928 | |||
bd8d6f92da | |||
f9c8e4f85a | |||
1fa7b7367a | |||
0765d6d1da | |||
f98f0e1a87 | |||
fb7ada72dd | |||
338de3ebf5 | |||
fb43c87107 | |||
f092c40999 | |||
f414f46cba | |||
9ec364b60e | |||
ae7e617fdd | |||
d1642c928a | |||
5e619f2593 | |||
679a4c7f31 | |||
76b890fe06 | |||
b2d2582e0f | |||
715b44ec79 | |||
25b7bf76bf | |||
b69940a5da | |||
28f2bb3ebd | |||
6bf940bedf | |||
56fc223930 | |||
3e00576230 | |||
6e7adaec59 | |||
9fd9124643 | |||
ad3d303405 | |||
7c47c6a033 | |||
33f17e4c5c | |||
289b2a4eb1 | |||
e63f51821f | |||
8370f4fc76 | |||
5feebeba01 | |||
0c0ef38b4f | |||
bec5ca1420 | |||
895f0f0079 | |||
134c2fcd42 | |||
2625e11508 | |||
9fa3a75fb6 | |||
b388be93c8 | |||
5a1242109d | |||
4f0ed402bf | |||
f6c500d120 | |||
785f33177d | |||
328f090723 | |||
ce3242c748 | |||
71ae99012b | |||
ab0141a5c6 | |||
abb1c3f3c4 | |||
1feb1f9c3f | |||
090ee1d4e9 | |||
7821ca12fd | |||
c01af6ac78 | |||
74099be316 | |||
4e6b34b9d1 | |||
6743dc6273 | |||
288ebe48fd | |||
9ae9830de4 | |||
0584fc3666 | |||
2e417a9143 | |||
0e46fc4c0a | |||
79735c6338 | |||
4505d13385 | |||
8fe0bd5abe | |||
98176b9760 | |||
104ffc930d | |||
2741780553 | |||
625242a3d9 | |||
401f878a9c | |||
3e97503220 | |||
76ade2957e | |||
b1e009f8b4 | |||
90f420b287 | |||
73b55fd975 | |||
ad601c7d5a | |||
046aac74c2 | |||
0998ffb5f2 | |||
8ff6fbb153 | |||
9d3a8e7c40 | |||
56aaa9dce3 | |||
43ead66991 | |||
92eb7d55dc | |||
1cfc85a772 | |||
47968c9447 | |||
07fee8ed3d | |||
1eb374fa49 | |||
285da114e7 | |||
34870591b4 | |||
ee87c253fe | |||
9c1f870694 | |||
56e0680398 | |||
8c0a1ed37d | |||
625500212d | |||
b1f5b3ad96 | |||
02f84a3b53 | |||
7fe662598b | |||
5bfa67b442 | |||
a53e40e1ee | |||
273c8a19b8 | |||
53bdd58c1b | |||
51879845f2 | |||
f2372841f6 | |||
0497d14a08 | |||
0b6b95110f | |||
191d80e5f5 | |||
659ba34fb3 | |||
907a1d1a4b | |||
99d2e8388a | |||
38b1d24953 | |||
b682f3e982 | |||
5f0d1d9536 | |||
c2c2d0b21e | |||
8c8a4dcbd1 | |||
7e9275831b | |||
6266f9e6a1 | |||
9649e190ef | |||
1547045165 | |||
5b0cab3e8a | |||
97b066b8fa | |||
4be0ab87fc | |||
3d465f6fdf | |||
e3bc88e36c | |||
eaf1e080ba | |||
02ef1172ce | |||
8f6f72ca48 | |||
df4f0eac20 | |||
d353e9314b | |||
567c1fd7a5 | |||
ab95cbee92 | |||
9dc1684179 | |||
c20b256b24 | |||
02d3fea4a9 | |||
ca98ff5864 | |||
6cd3b4dd95 | |||
480d197ffa | |||
a7c0e87f40 | |||
af1a48d918 | |||
e17ffc85e7 | |||
9c654ba72c | |||
b47ef9220e | |||
0204bade8b | |||
481a25d4df | |||
3dd161d65a | |||
53dd9c3427 | |||
4827b76b80 | |||
1206107a73 | |||
5621f0661e | |||
875947dd61 | |||
61b1d83bbd | |||
64e5fed7ac | |||
f6c36670c3 | |||
2e39a678db | |||
6dacb9894e | |||
612b13808f | |||
90a011c4e4 | |||
56f1c726b2 | |||
6323183119 | |||
3439a9ca27 | |||
9772bbe157 | |||
fa297a7b6a | |||
b48c920292 | |||
c0be04f46c | |||
b437837809 | |||
33f8059846 | |||
4eab0a3704 | |||
933e06a7ef | |||
ad8baf8091 | |||
598245531a | |||
4f044c0cd6 | |||
77fc793e89 | |||
aa95ce3d02 | |||
4c5bffe0f5 | |||
ed18a504e4 | |||
aec7e72dcf | |||
f2722f4766 | |||
07c516ccdd | |||
81330d30cf | |||
8f2c005d82 | |||
a4a813708c | |||
664e5354d3 | |||
10251b906a | |||
c9240c2dce | |||
220e6224e7 | |||
d3518c4944 | |||
5f34e58bd3 | |||
438404b5ba | |||
651d0472cd | |||
5cf9deb329 | |||
7409003949 | |||
3f1bf00fdd | |||
04576ba7fd | |||
053dc1d8dd | |||
834a865dfa | |||
e22b555b58 | |||
297f202005 | |||
440442bb99 | |||
d2f648edec | |||
53c91dc0c2 | |||
adbfde94d6 | |||
1b3ec2d0fb | |||
ecccb80e04 | |||
11914ca188 | |||
fb9627b7c4 | |||
b782e5e8af | |||
b67c686d67 | |||
c4d7711d2f | |||
f2da46b5f9 | |||
a0b2d4c8f2 | |||
1f85f4a007 | |||
2c2f517e52 | |||
bfbb29b2cf | |||
dd563ec9ae | |||
e2d36aa213 | |||
2cc2844abf | |||
75b8932395 | |||
311d8484a2 | |||
917282303d | |||
8afe31192b | |||
bbbf64f543 | |||
6b0ab10231 | |||
acedff4e89 | |||
f9bfda059f | |||
94c91e0dae | |||
48fadd1a11 | |||
51f5594ea0 | |||
91114e2afe | |||
e058fa9069 | |||
2ba7ec48f6 | |||
e125862794 | |||
ab6220a4cb | |||
647a066c90 | |||
2b6eee4237 | |||
7eb1be4633 | |||
0ecc53f59c | |||
e3983deacc | |||
9357059cbc | |||
9061d5c972 | |||
471170a3e0 | |||
43521c6e09 | |||
fc5edcdf0f | |||
3d50393b33 | |||
424fd1347d | |||
3475b169ea | |||
c1fa728c24 | |||
3b12f5eec7 | |||
12b03a844d | |||
4716ceb950 | |||
c91a409258 | |||
784d55c16c | |||
99925d7cf9 | |||
ae38917a33 | |||
9da727b623 | |||
241ee1cb9d | |||
e959fe2e9d | |||
6978ec4246 | |||
2b8bbc550c | |||
25f6528049 | |||
3289cbd6e7 | |||
b91e7e9fb8 | |||
e70a483d6b | |||
6244913ab9 | |||
33fbcc8ba3 | |||
3f7d20ec5b | |||
466be1dca5 | |||
bdbd521257 | |||
deba756598 | |||
3cb99ff64f | |||
20e903914c | |||
71ea1c5f9b | |||
4a4c0a3da1 | |||
8f2754493c | |||
dde94c0d0f | |||
00d66ffd48 | |||
c7c7211978 | |||
02a81dd9e1 | |||
02555fcbac | |||
b9d76f5047 | |||
9f9f4c0755 | |||
1691537a1e | |||
eb2344a43f | |||
c0fd06fd2d | |||
275521db70 | |||
268730e71b | |||
0feb10315b | |||
39f69a6c3b | |||
d9fecabc1f | |||
14611d1f7b | |||
fe6e27fd6a | |||
0e06908a48 | |||
390c6a1977 | |||
af338b0607 | |||
b45cc0e63f | |||
128dea2677 | |||
cd5e808bb6 | |||
bbf2f96288 | |||
2404eeadf0 | |||
877de1f597 | |||
3519ad7c8d | |||
3c16701766 | |||
1da0b5be76 | |||
c6e428c047 | |||
45ea5c356e | |||
5da29e8063 | |||
99087019d2 | |||
71f8ce001d | |||
bb39db3f42 | |||
b0e80e49b3 | |||
ec53ce9d00 | |||
52d9adad70 | |||
118a4392a2 | |||
b8c3bdbbf6 | |||
d49007538b | |||
6f103101b6 | |||
a5a9fa3750 | |||
efcac6b3e4 | |||
8cb884f10e | |||
6d8000b957 | |||
f5bd215f36 | |||
532a028e41 | |||
2581bf69e0 | |||
2da4823e33 | |||
70c4b7528d | |||
5dfc0b3c0e | |||
44d1d063e9 | |||
71e50b1bb9 | |||
64c307077f | |||
71e364b42f | |||
054d339b48 | |||
e3b9031e71 | |||
3fa9d7a1d4 | |||
6eafa3c43d | |||
207579c59f | |||
2780ca30a8 | |||
e0e41d9e5c | |||
340830d45f | |||
de4662b9d5 | |||
c66e82c4ae | |||
a2210a6b0d | |||
41c54029b5 | |||
f9142fedfd | |||
102bc9c01b | |||
ef26519993 | |||
e27367e3f2 | |||
e70fd8e351 | |||
dc9db9a438 | |||
6fe455dac0 | |||
1436667e7d | |||
a9a1fe2aa0 | |||
d68b447874 | |||
11e3a5def8 | |||
6aae943e77 | |||
8d0322c0c3 | |||
f60a5f568f | |||
00fd866cc8 | |||
8929898397 | |||
73d5110d67 | |||
0c559976d6 | |||
540989f38a | |||
608bbdd4ee | |||
9a5f96f80b | |||
6bf1deb99b | |||
cfca4927e2 | |||
93cb7b8ce6 | |||
dcc430f91b | |||
64216f74ae | |||
8fd15f4082 | |||
38abf31889 | |||
c39cfbe2ae | |||
b00bff0af5 | |||
56a2a3a065 | |||
a64f509872 | |||
359cc67fab | |||
72cd409735 | |||
ceac06caf6 | |||
694df9d845 | |||
360d2a3c2e | |||
9cfc67329a | |||
75c74c25e1 | |||
7dce8c29bc | |||
41337d0ca4 | |||
287feace12 | |||
b3e2eca43d | |||
0818c55b6d | |||
b1b526a516 | |||
b6cbed1c90 | |||
65e2abf402 | |||
bee5da8f64 | |||
ab7b807740 | |||
4c02721ada | |||
5dca31e6f9 | |||
d2e1aff453 | |||
dab80466c5 | |||
2c83ba0824 | |||
657f4d3e9c | |||
9c9c09db2b | |||
c38a601bcc | |||
dcac9aed55 | |||
0225fc7120 | |||
2a2b4e7bd9 | |||
7d872420f0 | |||
0055514f90 | |||
6248cc5552 | |||
347967700b | |||
eaff5ac9f0 | |||
63964ac101 | |||
3b52fa4be8 | |||
81ddb8c5fb | |||
eae8f993e6 | |||
fab1d249f4 | |||
3f1cc466ed | |||
c64d6b6b28 | |||
c9658918ed | |||
6d3028c2d7 | |||
aec7b73345 | |||
56563412aa | |||
a9b8c57464 | |||
314771cce2 | |||
bb65dc5247 | |||
460a72e6b5 | |||
748090d0f9 | |||
968258852f | |||
260cc0dd5f | |||
57cea677e4 | |||
c47eeb9cec | |||
e929019247 | |||
43659dffd3 | |||
82d89825d3 | |||
772670252a | |||
18591d0287 | |||
08dacacdda | |||
3091a337c9 | |||
73580493ea | |||
c188bb290f | |||
d9c8867c0b | |||
87c9f6b2a0 | |||
6830957d31 | |||
96a67a44ac | |||
b9b8527b38 | |||
10520fb1bd | |||
494d893aa3 | |||
9048a1dbbe | |||
04b4f32e16 | |||
d110dac0a6 | |||
be6a4efacb | |||
f75fe1201d | |||
547272b17b | |||
94db36d6c4 | |||
3efcbaaea9 | |||
fde09b4a94 | |||
317503aa2b | |||
be2dad9b17 | |||
2e2d5b9f86 | |||
a7b81a4671 | |||
86a60f7ebd | |||
a6366e47fe | |||
bc467340c9 | |||
4154df7c21 | |||
79e273b1ca | |||
4ab3c39415 | |||
ad243ae41f | |||
2898eae566 | |||
cbd62b9e93 | |||
27374aea3f | |||
a4ef259bd2 | |||
3f10829dcd | |||
289c820b48 | |||
40d4e3fe83 | |||
f4170f55b7 | |||
d8bff8201f | |||
dffcc3f405 | |||
7030258de5 | |||
89a2aad561 | |||
6406c4021a | |||
ddc5a0d30b | |||
d4b1f074e3 | |||
3b68b304f5 | |||
b8cceaa17a | |||
8a7675d2e4 | |||
6cf91e5eca | |||
e938866081 | |||
20108c2603 | |||
02c7deb783 | |||
368cf4b3f0 | |||
c093e07c24 | |||
a97142272c | |||
97fa94592e | |||
0c76178bee | |||
c12bc5e02d | |||
0db1d98f41 | |||
008f6e2bdd | |||
820709d433 | |||
d5838262ea | |||
617f71fd51 | |||
1ac143a065 | |||
eca59114dd | |||
a199eaa171 | |||
26ae034ba9 | |||
99b1c0bed4 | |||
388eae6a1c | |||
ff6d1f611a | |||
157068634a | |||
1d4882e596 | |||
415d9e702b | |||
4c3f7171e7 | |||
a146fc43e4 | |||
ef3fb561e9 | |||
fffdf4b8c1 | |||
3d7562ea8e | |||
fd3fce6deb | |||
753e70775a | |||
2c0bc93f5a | |||
adc51529f2 | |||
63bf4355b9 | |||
41b06bca60 | |||
d6f6764b0c | |||
46226c4efc | |||
f59977f884 | |||
40f1fcab18 | |||
85b119bdcb | |||
6c7bc2b40c | |||
f06211ce4f | |||
d2db5e32fc | |||
4458e28ce2 | |||
14ac3a8ae6 | |||
ca890e1ef4 | |||
9cc2cf8360 | |||
28c837a47f | |||
42cf45c8f3 | |||
8fdce937bd | |||
f36bec7176 | |||
9477316b61 | |||
36d1d52880 | |||
abb44839af | |||
e91276bb76 | |||
b5f40f9732 | |||
24ffd96b6e | |||
c977c651a2 | |||
5c78991ae1 | |||
c467f179e1 | |||
264265a1f7 | |||
a12a9da627 | |||
c42294a21e | |||
03437885ef | |||
5e81fc8dc2 | |||
7a5ff282b6 | |||
e4069ab51b |
53
.buildkite/pipeline.yml
Normal file
@ -0,0 +1,53 @@
|
||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||
# Last docker plugin version can be found here:
|
||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||
|
||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||
|
||||
steps:
|
||||
- label: "Assemble GPlay Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
- label: "Assemble FDroid Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
- label: "Build Google Play unsigned APK"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/release/*.apk"
|
||||
branches: "master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
# Code quality
|
||||
|
||||
- label: "Code quality"
|
||||
command: "./tools/check/check_code_quality.sh"
|
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
### Pull Request Checklist
|
||||
|
||||
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
|
||||
|
||||
- [ ] Changes has been tested on an Android device or Android emulator with API 16
|
||||
- [ ] UI change has been tested on both light and dark themes
|
||||
- [ ] Pull request is based on the develop branch
|
||||
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
2
.gitignore
vendored
@ -10,3 +10,5 @@
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
/tmp
|
||||
|
29
.idea/codeStyles/Project.xml
generated
@ -1,29 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
13
.idea/dictionaries/ganfra.xml
generated
@ -1,13 +0,0 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="ganfra">
|
||||
<words>
|
||||
<w>connectable</w>
|
||||
<w>coroutine</w>
|
||||
<w>merlins</w>
|
||||
<w>moshi</w>
|
||||
<w>persistor</w>
|
||||
<w>synchronizer</w>
|
||||
<w>untimelined</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
20
.idea/gradle.xml
generated
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/matrix-sdk-android" />
|
||||
<option value="$PROJECT_DIR$/matrix-sdk-android-rx" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DifferentStdlibGradleVersion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
34
.idea/misc.xml
generated
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="5">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
generated
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
60
.travis.yml
Normal file
@ -0,0 +1,60 @@
|
||||
# FTR: Configuration on https://travis-ci.org/vector-im/riotX-android/settings
|
||||
#
|
||||
# - Build only if .travis.yml is present -> On
|
||||
# - Limit concurrent jobs -> Off
|
||||
# - Build pushed branches -> On (build the branch)
|
||||
# - Build pushed pull request -> On (build the PR after auto-merge)
|
||||
#
|
||||
# - Auto cancel branch builds -> On
|
||||
# - Auto cancel pull request builds -> On
|
||||
|
||||
language: android
|
||||
jdk: oraclejdk8
|
||||
sudo: false
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
android:
|
||||
components:
|
||||
# Uncomment the lines below if you want to
|
||||
# use the latest revision of Android SDK Tools
|
||||
- tools
|
||||
- platform-tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-28.0.3
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-28
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- $HOME/.android/build-cache
|
||||
|
||||
# Build with the development SDK
|
||||
before_script:
|
||||
# Not necessary for the moment
|
||||
# - /bin/sh ./set_debug_env.sh
|
||||
|
||||
# Just build the project for now
|
||||
script:
|
||||
# Build app (assembleGplayRelease assembleFdroidRelease)
|
||||
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||
# Other code quality check
|
||||
- ./tools/check/check_code_quality.sh
|
||||
- ./tools/travis/check_pr.sh
|
||||
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
||||
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
0
AUTHORS.md
Normal file
36
CHANGES.md
Normal file
@ -0,0 +1,36 @@
|
||||
Changes in RiotX 0.1.0 (2019-07-11)
|
||||
===================================================
|
||||
|
||||
First release!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
=======================================================
|
||||
+ TEMPLATE WHEN PREPARING A NEW RELEASE +
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
-
|
||||
|
||||
Improvements:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
-
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
|
76
CONTRIBUTING.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Contributing code to Matrix
|
||||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||
|
||||
Android support can be found in this [](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
Dedicated room for RiotX: [](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
# Specific rules for Matrix Android projects
|
||||
|
||||
## Android Studio settings
|
||||
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
|
||||
## Compilation
|
||||
|
||||
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
|
||||
## I want to help translating RiotX
|
||||
|
||||
If you want to fix an issue with an English string, please submit a PR.
|
||||
If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
|
||||
For the moment, Strings from Riot will be used, there is no dedicated project in Weblate for RiotX.
|
||||
|
||||
## I want to submit a PR to fix an issue
|
||||
|
||||
Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it.
|
||||
If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it.
|
||||
|
||||
### Kotlin
|
||||
|
||||
This project is full Kotlin. Please do not write Java classes.
|
||||
|
||||
### CHANGES.md
|
||||
|
||||
Please add a line to the top of the file `CHANGES.md` describing your change.
|
||||
|
||||
### Code quality
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./tools/check/check_code_quality.sh
|
||||
|
||||
> ./gradlew lintGplayRelease
|
||||
|
||||
### Unit tests
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./gradlew testGplayReleaseUnitTest
|
||||
|
||||
### Tests
|
||||
|
||||
RiotX is currently supported on Android Jelly Bean (API 16+): please test your change on an Android device (or Android emulator) running with API 16. Many issues can happen (including crashes) on older devices.
|
||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||
|
||||
### Internationalisation
|
||||
|
||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
Do not hesitate to use plurals when appropriate.
|
||||
|
||||
### Layout
|
||||
|
||||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||
You can check this in the layout editor preview by selecting any RTL language (ex: Arabic).
|
||||
|
||||
Also please check that the colors are ok for all the current themes of RiotX. Please use `?attr` instead of `@color` to reference colors in the layout. You can check this in the layout editor preview by selecting all the main themes (`AppTheme.Status`, `AppTheme.Dark`, etc.).
|
||||
|
||||
### Authors
|
||||
|
||||
Feel free to add an entry in file AUTHORS.md
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks for contributing to Matrix projects!
|
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
21
README.md
@ -1 +1,20 @@
|
||||
# riot-android-redesign-PoC
|
||||
[](https://buildkite.com/matrix-dot-org/riotx-android)
|
||||
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||
[](https://matrix.to/#/#riotx:matrix.org)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
|
||||
# RiotX Android
|
||||
|
||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
||||
|
||||
It's based on a new Matrix SDK, written in Kotlin.
|
||||
|
||||
Download nightly build here: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
Matrix Room: [](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
||||
|
105
app/build.gradle
@ -1,105 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
||||
def versionMajor = 0
|
||||
def versionMinor = 1
|
||||
def versionPatch = 0
|
||||
|
||||
def generateVersionCodeFromTimestamp() {
|
||||
// It's unix timestamp divided by 10: It's incremented by one every 10 seconds.
|
||||
return (System.currentTimeMillis() / 1_000 / 10).toInteger()
|
||||
}
|
||||
|
||||
def generateVersionCodeFromVersionName() {
|
||||
return versionMajor * 10000 + versionMinor * 100 + versionPatch
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "im.vector.riotredesign"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
multiDexEnabled true
|
||||
versionCode generateVersionCodeFromTimestamp()
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
def epoxy_version = "3.0.0"
|
||||
def arrow_version = "0.8.2"
|
||||
def glide_version = "4.8.0"
|
||||
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation project(":matrix-sdk-android-rx")
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.core:core-ktx:1.0.1'
|
||||
|
||||
// Paging
|
||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
||||
|
||||
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
// rx
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0'
|
||||
|
||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
||||
implementation 'com.airbnb.android:mvrx:0.7.0'
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
||||
// UI
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha02'
|
||||
|
||||
// DI
|
||||
implementation "org.koin:koin-android:$koin_version"
|
||||
implementation "org.koin:koin-android-scope:$koin_version"
|
||||
|
||||
// TESTS
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
}
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="im.vector.riotredesign">
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".Riot"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Riot">
|
||||
|
||||
<activity
|
||||
android:name=".features.MainActivity"
|
||||
android:theme="@style/Theme.Riot.Splash">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.home.HomeActivity" />
|
||||
<activity android:name=".features.login.LoginActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDex
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
import org.koin.log.EmptyLogger
|
||||
import org.koin.standalone.StandAloneContext.startKoin
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class Riot : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
AndroidThreeTen.init(this)
|
||||
startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import im.vector.riotredesign.core.resources.LocaleProvider
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
|
||||
import org.koin.dsl.module.module
|
||||
|
||||
class AppModule(private val context: Context) {
|
||||
|
||||
val definition = module {
|
||||
|
||||
single {
|
||||
LocaleProvider(context.resources)
|
||||
}
|
||||
|
||||
single {
|
||||
StringProvider(context.resources)
|
||||
}
|
||||
|
||||
single {
|
||||
context.getSharedPreferences("im.vector.riot", MODE_PRIVATE)
|
||||
}
|
||||
|
||||
single {
|
||||
RoomSelectionRepository(get())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.epoxy
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
abstract class KotlinModel(
|
||||
@LayoutRes private val layoutRes: Int
|
||||
) : EpoxyModel<View>() {
|
||||
|
||||
private var view: View? = null
|
||||
private var onBindCallback: (() -> Unit)? = null
|
||||
private var onModelVisibilityStateChangedListener: OnModelVisibilityStateChangedListener<KotlinModel, View>? = null
|
||||
|
||||
abstract fun bind()
|
||||
|
||||
override fun bind(view: View) {
|
||||
this.view = view
|
||||
onBindCallback?.invoke()
|
||||
bind()
|
||||
}
|
||||
|
||||
override fun unbind(view: View) {
|
||||
this.view = null
|
||||
}
|
||||
|
||||
fun onBind(lambda: (() -> Unit)?): KotlinModel {
|
||||
onBindCallback = lambda
|
||||
return this
|
||||
}
|
||||
|
||||
override fun onVisibilityStateChanged(visibilityState: Int, view: View) {
|
||||
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(this, view, visibilityState)
|
||||
super.onVisibilityStateChanged(visibilityState, view)
|
||||
}
|
||||
|
||||
fun setOnVisibilityStateChanged(listener: OnModelVisibilityStateChangedListener<KotlinModel, View>): KotlinModel {
|
||||
this.onModelVisibilityStateChangedListener = listener
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getDefaultLayout() = layoutRes
|
||||
|
||||
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
|
||||
override fun getValue(thisRef: KotlinModel, property: KProperty<*>): V {
|
||||
// This is not efficient because it looks up the view by id every time (it loses
|
||||
// the pattern of a "holder" to cache that look up). But it is simple to use and could
|
||||
// be optimized with a map
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return view?.findViewById(id) as V?
|
||||
?: throw IllegalStateException("View ID $id for '${property.name}' not found.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2019 New Vector Ltd
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.extensions
|
||||
|
||||
/**
|
||||
* Returns the last element yielding the smallest value of the given function or `null` if there are no elements.
|
||||
*/
|
||||
public inline fun <T, R : Comparable<R>> Iterable<T>.lastMinBy(selector: (T) -> R): T? {
|
||||
val iterator = iterator()
|
||||
if (!iterator.hasNext()) return null
|
||||
var minElem = iterator.next()
|
||||
var minValue = selector(minElem)
|
||||
while (iterator.hasNext()) {
|
||||
val e = iterator.next()
|
||||
val v = selector(e)
|
||||
if (minValue >= v) {
|
||||
minElem = e
|
||||
minValue = v
|
||||
}
|
||||
}
|
||||
return minElem
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.platform
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import com.airbnb.mvrx.BaseMvRxFragment
|
||||
import com.airbnb.mvrx.MvRx
|
||||
|
||||
abstract class RiotFragment : BaseMvRxFragment(), OnBackPressed {
|
||||
|
||||
val riotActivity: RiotActivity by lazy {
|
||||
activity as RiotActivity
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
//no-ops by default
|
||||
}
|
||||
|
||||
protected fun setArguments(args: Parcelable? = null) {
|
||||
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features
|
||||
|
||||
import android.os.Bundle
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.core.platform.RiotActivity
|
||||
import im.vector.riotredesign.features.home.HomeActivity
|
||||
import im.vector.riotredesign.features.login.LoginActivity
|
||||
|
||||
|
||||
class MainActivity : RiotActivity() {
|
||||
|
||||
private val authenticator = Matrix.getInstance().authenticator()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val intent = if (authenticator.hasActiveSessions()) {
|
||||
HomeActivity.newIntent(this)
|
||||
} else {
|
||||
LoginActivity.newIntent(this)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.firstCharAsString
|
||||
import im.vector.riotredesign.core.glide.GlideApp
|
||||
|
||||
object AvatarRenderer {
|
||||
|
||||
fun render(roomMember: RoomMember, imageView: ImageView) {
|
||||
render(roomMember.avatarUrl, roomMember.displayName, imageView)
|
||||
}
|
||||
|
||||
fun render(roomSummary: RoomSummary, imageView: ImageView) {
|
||||
render(roomSummary.avatarUrl, roomSummary.displayName, imageView)
|
||||
}
|
||||
|
||||
fun render(avatarUrl: String?, name: String?, imageView: ImageView) {
|
||||
if (name.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
|
||||
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
||||
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
||||
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
.load(resolvedUrl)
|
||||
.placeholder(fallbackDrawable)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.observeEvent
|
||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||
import im.vector.riotredesign.core.platform.RiotActivity
|
||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragment
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.standalone.StandAloneContext.loadKoinModules
|
||||
|
||||
|
||||
class HomeActivity : RiotActivity(), ToolbarConfigurable {
|
||||
|
||||
|
||||
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
||||
private val homeNavigator by inject<HomeNavigator>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
loadKoinModules(listOf(HomeModule().definition))
|
||||
homeNavigator.activity = this
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_home)
|
||||
if (savedInstanceState == null) {
|
||||
val homeDrawerFragment = HomeDrawerFragment.newInstance()
|
||||
val loadingDetail = LoadingRoomDetailFragment.newInstance()
|
||||
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
|
||||
replaceFragment(homeDrawerFragment, R.id.homeDrawerFragmentContainer)
|
||||
}
|
||||
homeActivityViewModel.openRoomLiveData.observeEvent(this) {
|
||||
homeNavigator.openRoomDetail(it, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
homeNavigator.activity = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: Toolbar) {
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, 0, 0)
|
||||
drawerLayout.addDrawerListener(drawerToggle)
|
||||
drawerToggle.syncState()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
drawerLayout.openDrawer(GravityCompat.START)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(Gravity.LEFT)) {
|
||||
drawerLayout.closeDrawer(Gravity.LEFT)
|
||||
} else {
|
||||
val handled = recursivelyDispatchOnBackPressed(supportFragmentManager)
|
||||
if (!handled) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recursivelyDispatchOnBackPressed(fm: FragmentManager): Boolean {
|
||||
if (fm.backStackEntryCount == 0)
|
||||
return false
|
||||
val reverseOrder = fm.fragments.filter { it is OnBackPressed }.reversed()
|
||||
for (f in reverseOrder) {
|
||||
val handledByChildFragments = recursivelyDispatchOnBackPressed(f.childFragmentManager)
|
||||
if (handledByChildFragments) {
|
||||
return true
|
||||
}
|
||||
val backPressable = f as OnBackPressed
|
||||
if (backPressable.onBackPressed()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context): Intent {
|
||||
return Intent(context, HomeActivity::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
class EmptyState : MvRxState
|
||||
|
||||
class HomeActivityViewModel(state: EmptyState,
|
||||
private val session: Session,
|
||||
roomSelectionRepository: RoomSelectionRepository
|
||||
) : RiotViewModel<EmptyState>(state) {
|
||||
|
||||
companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? {
|
||||
val session = Matrix.getInstance().currentSession
|
||||
val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>()
|
||||
return HomeActivityViewModel(state, session, roomSelectionRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
|
||||
val openRoomLiveData: LiveData<LiveEvent<String>>
|
||||
get() = _openRoomLiveData
|
||||
|
||||
init {
|
||||
val lastSelectedRoomId = roomSelectionRepository.lastSelectedRoom()
|
||||
if (lastSelectedRoomId == null || session.getRoom(lastSelectedRoomId) == null) {
|
||||
getTheFirstRoomWhenAvailable()
|
||||
} else {
|
||||
_openRoomLiveData.postValue(LiveEvent(lastSelectedRoomId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTheFirstRoomWhenAvailable() {
|
||||
session.rx().liveRoomSummaries()
|
||||
.filter { it.isNotEmpty() }
|
||||
.first(emptyList())
|
||||
.subscribeBy {
|
||||
val firstRoom = it.firstOrNull()
|
||||
if (firstRoom != null) {
|
||||
_openRoomLiveData.postValue(LiveEvent(firstRoom.roomId))
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.replaceChildFragment
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import im.vector.riotredesign.features.home.group.GroupListFragment
|
||||
import im.vector.riotredesign.features.home.room.list.RoomListFragment
|
||||
|
||||
class HomeDrawerFragment : RiotFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(): HomeDrawerFragment {
|
||||
return HomeDrawerFragment()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_home_drawer, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
val groupListFragment = GroupListFragment.newInstance()
|
||||
replaceChildFragment(groupListFragment, R.id.groupListFragmentContainer)
|
||||
val roomListFragment = RoomListFragment.newInstance()
|
||||
replaceChildFragment(roomListFragment, R.id.roomListFragmentContainer)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomNameItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
|
||||
import org.koin.dsl.module.module
|
||||
|
||||
class HomeModule {
|
||||
|
||||
val definition = module(override = true) {
|
||||
|
||||
single {
|
||||
TimelineDateFormatter(get())
|
||||
}
|
||||
|
||||
single {
|
||||
MessageItemFactory(get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomNameItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomTopicItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomMemberItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
DefaultItemFactory()
|
||||
}
|
||||
|
||||
single {
|
||||
TimelineItemFactory(get(), get(), get(), get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
HomeNavigator()
|
||||
}
|
||||
|
||||
factory {
|
||||
RoomSummaryController(get())
|
||||
}
|
||||
|
||||
factory { (roomId: String) ->
|
||||
TimelineEventController(roomId, get(), get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
TimelineMediaSizeProvider()
|
||||
}
|
||||
|
||||
single {
|
||||
SelectedGroupHolder()
|
||||
}
|
||||
|
||||
single {
|
||||
VisibleRoomHolder()
|
||||
}
|
||||
|
||||
single {
|
||||
HomePermalinkHandler(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomSummaryComparator()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import android.view.Gravity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.addFragmentToBackstack
|
||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||
import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs
|
||||
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import timber.log.Timber
|
||||
|
||||
class HomeNavigator {
|
||||
|
||||
var activity: HomeActivity? = null
|
||||
|
||||
private var currentRoomId: String? = null
|
||||
|
||||
fun openRoomDetail(roomId: String,
|
||||
eventId: String?,
|
||||
addToBackstack: Boolean = false) {
|
||||
Timber.v("Open room detail $roomId - $eventId - $addToBackstack")
|
||||
if (!addToBackstack && isRoomOpened(roomId)) {
|
||||
return
|
||||
}
|
||||
activity?.let {
|
||||
val args = RoomDetailArgs(roomId, eventId)
|
||||
val roomDetailFragment = RoomDetailFragment.newInstance(args)
|
||||
it.drawerLayout?.closeDrawer(Gravity.LEFT)
|
||||
if (addToBackstack) {
|
||||
it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId)
|
||||
} else {
|
||||
currentRoomId = roomId
|
||||
clearBackStack(it.supportFragmentManager)
|
||||
it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openGroupDetail(groupId: String) {
|
||||
Timber.v("Open group detail $groupId")
|
||||
}
|
||||
|
||||
fun openUserDetail(userId: String) {
|
||||
Timber.v("Open user detail $userId")
|
||||
}
|
||||
|
||||
fun isRoomOpened(roomId: String): Boolean {
|
||||
return currentRoomId == roomId
|
||||
}
|
||||
|
||||
private fun clearBackStack(fragmentManager: FragmentManager) {
|
||||
if (fragmentManager.backStackEntryCount > 0) {
|
||||
val first = fragmentManager.getBackStackEntryAt(0)
|
||||
fragmentManager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home
|
||||
|
||||
import android.net.Uri
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkData
|
||||
import im.vector.matrix.android.api.permalinks.PermalinkParser
|
||||
|
||||
class HomePermalinkHandler(private val navigator: HomeNavigator) {
|
||||
|
||||
fun launch(deepLink: String?) {
|
||||
val uri = deepLink?.let { Uri.parse(it) }
|
||||
launch(uri)
|
||||
}
|
||||
|
||||
fun launch(deepLink: Uri?) {
|
||||
if (deepLink == null) {
|
||||
return
|
||||
}
|
||||
val permalinkData = PermalinkParser.parse(deepLink)
|
||||
when (permalinkData) {
|
||||
is PermalinkData.EventLink -> {
|
||||
navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, true)
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
navigator.openRoomDetail(permalinkData.roomIdOrAlias, null, true)
|
||||
}
|
||||
is PermalinkData.GroupLink -> {
|
||||
navigator.openGroupDetail(permalinkData.groupId)
|
||||
}
|
||||
is PermalinkData.UserLink -> {
|
||||
navigator.openUserDetail(permalinkData.userId)
|
||||
}
|
||||
is PermalinkData.FallbackLink -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.group
|
||||
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
class GroupListViewModel(initialState: GroupListViewState,
|
||||
private val selectedGroupHolder: SelectedGroupHolder,
|
||||
private val session: Session
|
||||
) : RiotViewModel<GroupListViewState>(initialState) {
|
||||
|
||||
companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? {
|
||||
val currentSession = Matrix.getInstance().currentSession
|
||||
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupHolder>()
|
||||
return GroupListViewModel(state, selectedGroupHolder, currentSession)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeGroupSummaries()
|
||||
observeState()
|
||||
}
|
||||
|
||||
private fun observeState() {
|
||||
subscribe {
|
||||
selectedGroupHolder.setSelectedGroup(it.selectedGroup)
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(action: GroupListActions) {
|
||||
when (action) {
|
||||
is GroupListActions.SelectGroup -> handleSelectGroup(action)
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state ->
|
||||
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
|
||||
setState { copy(selectedGroup = action.groupSummary) }
|
||||
} else {
|
||||
setState { copy(selectedGroup = null) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun observeGroupSummaries() {
|
||||
session
|
||||
.rx().liveGroupSummaries()
|
||||
.execute { async ->
|
||||
copy(asyncGroups = async)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.group
|
||||
|
||||
import android.widget.ImageView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.platform.CheckableFrameLayout
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
|
||||
data class GroupSummaryItem(
|
||||
val groupName: CharSequence,
|
||||
val avatarUrl: String?,
|
||||
val isSelected: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_group) {
|
||||
|
||||
private val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
|
||||
private val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout)
|
||||
|
||||
override fun bind() {
|
||||
rootView.isSelected = isSelected
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
AvatarRenderer.render(avatarUrl, groupName.toString(), avatarImageView)
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.group
|
||||
|
||||
import arrow.core.Option
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import io.reactivex.Observable
|
||||
|
||||
class SelectedGroupHolder {
|
||||
|
||||
private val selectedGroupStream = BehaviorRelay.createDefault<Option<GroupSummary>>(Option.empty())
|
||||
|
||||
fun setSelectedGroup(group: GroupSummary?) {
|
||||
val optionValue = Option.fromNullable(group)
|
||||
selectedGroupStream.accept(optionValue)
|
||||
}
|
||||
|
||||
fun selectedGroup(): Observable<Option<GroupSummary>> {
|
||||
return selectedGroupStream.hide()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bumptech.glide.Glide
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import kotlinx.android.synthetic.main.fragment_loading_room_detail.*
|
||||
|
||||
class LoadingRoomDetailFragment : RiotFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(): LoadingRoomDetailFragment {
|
||||
return LoadingRoomDetailFragment()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_loading_room_detail, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Glide.with(this)
|
||||
.load(R.drawable.riot_splash)
|
||||
.into(animatedLogoImageView)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.HomePermalinkHandler
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Parcelize
|
||||
data class RoomDetailArgs(
|
||||
val roomId: String,
|
||||
val eventId: String? = null
|
||||
) : Parcelable
|
||||
|
||||
class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(args: RoomDetailArgs): RoomDetailFragment {
|
||||
return RoomDetailFragment().apply {
|
||||
setArguments(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
|
||||
private val roomDetailArgs: RoomDetailArgs by args()
|
||||
|
||||
private val timelineEventController by inject<TimelineEventController> { parametersOf(roomDetailArgs.roomId) }
|
||||
private val homePermalinkHandler by inject<HomePermalinkHandler>()
|
||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_room_detail, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
setupRecyclerView()
|
||||
setupToolbar()
|
||||
setupSendButton()
|
||||
roomDetailViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
val parentActivity = riotActivity
|
||||
if (parentActivity is ToolbarConfigurable) {
|
||||
parentActivity.configure(toolbar)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
|
||||
epoxyVisibilityTracker.attach(recyclerView)
|
||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.setHasFixedSize(true)
|
||||
timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
|
||||
recyclerView.setController(timelineEventController)
|
||||
timelineEventController.callback = this
|
||||
}
|
||||
|
||||
private fun setupSendButton() {
|
||||
sendButton.setOnClickListener {
|
||||
val textMessage = composerEditText.text.toString()
|
||||
if (textMessage.isNotBlank()) {
|
||||
composerEditText.text = null
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(state: RoomDetailViewState) {
|
||||
renderRoomSummary(state)
|
||||
renderTimeline(state)
|
||||
}
|
||||
|
||||
private fun renderTimeline(state: RoomDetailViewState) {
|
||||
when (state.asyncTimelineData) {
|
||||
is Success -> {
|
||||
val timelineData = state.asyncTimelineData()
|
||||
val lockAutoScroll = timelineData?.let {
|
||||
it.events == timelineEventController.currentList && it.isLoadingForward
|
||||
} ?: true
|
||||
|
||||
scrollOnNewMessageCallback.isLocked.set(lockAutoScroll)
|
||||
timelineEventController.update(timelineData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderRoomSummary(state: RoomDetailViewState) {
|
||||
state.asyncRoomSummary()?.let {
|
||||
toolbarTitleView.text = it.displayName
|
||||
AvatarRenderer.render(it, toolbarAvatarImageView)
|
||||
if (it.topic.isNotEmpty()) {
|
||||
toolbarSubtitleView.visibility = View.VISIBLE
|
||||
toolbarSubtitleView.text = it.topic
|
||||
} else {
|
||||
toolbarSubtitleView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
|
||||
override fun onUrlClicked(url: String) {
|
||||
homePermalinkHandler.launch(url)
|
||||
}
|
||||
|
||||
override fun onEventVisible(event: TimelineEvent, index: Int) {
|
||||
roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index))
|
||||
}
|
||||
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail
|
||||
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.extensions.lastMinBy
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.koin.android.ext.android.get
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||
private val session: Session,
|
||||
private val visibleRoomHolder: VisibleRoomHolder
|
||||
) : RiotViewModel<RoomDetailViewState>(initialState), Room.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
|
||||
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
|
||||
val currentSession = Matrix.getInstance().currentSession
|
||||
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomHolder>()
|
||||
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeRoomSummary()
|
||||
observeTimeline()
|
||||
observeDisplayedEvents()
|
||||
room.loadRoomMembersIfNeeded()
|
||||
room.addListener(this)
|
||||
}
|
||||
|
||||
fun process(action: RoomDetailActions) {
|
||||
when (action) {
|
||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
room.removeListener(this)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
// Room.Listener *******************************************************************************
|
||||
|
||||
override fun onReadReceiptsUpdated() {
|
||||
Timber.v("On read receipts updated")
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
|
||||
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
||||
}
|
||||
|
||||
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||
displayedEventsObservable.accept(action)
|
||||
|
||||
}
|
||||
|
||||
private fun handleIsDisplayed() {
|
||||
visibleRoomHolder.setVisibleRoom(roomId)
|
||||
}
|
||||
|
||||
private fun observeDisplayedEvents() {
|
||||
// We are buffering scroll events for one second
|
||||
// and keep the most recent one to set the read receipt on.
|
||||
displayedEventsObservable.hide()
|
||||
.buffer(1, TimeUnit.SECONDS)
|
||||
.filter { it.isNotEmpty() }
|
||||
.subscribeBy(onNext = { actions ->
|
||||
val eventIds = actions.mapNotNull { it.event.root.eventId }
|
||||
withState { state ->
|
||||
val newMapOfReadReceipts = HashMap(state.readReceiptsForEventId)
|
||||
eventIds.forEach {
|
||||
if (newMapOfReadReceipts.containsKey(it).not()) {
|
||||
val readReceipts = room.readReceipts(it)
|
||||
newMapOfReadReceipts[it] = readReceipts
|
||||
}
|
||||
}
|
||||
setState { copy(readReceiptsForEventId = newMapOfReadReceipts) }
|
||||
}
|
||||
|
||||
val mostRecentEvent = actions.lastMinBy { it.index }
|
||||
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
||||
room.setReadReceipt(eventId, callback = object : MatrixCallback<Void> {})
|
||||
}
|
||||
})
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx().liveRoomSummary()
|
||||
.execute { async ->
|
||||
copy(asyncRoomSummary = async)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeTimeline() {
|
||||
room.rx().timeline(eventId)
|
||||
.execute { timelineData ->
|
||||
copy(asyncTimelineData = timelineData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
abstract class AbsMessageItem(private val informationData: MessageInformationData,
|
||||
@LayoutRes layoutRes: Int
|
||||
) : KotlinModel(layoutRes) {
|
||||
|
||||
protected abstract val avatarImageView: ImageView
|
||||
protected abstract val memberNameView: TextView
|
||||
protected abstract val timeView: TextView
|
||||
|
||||
override fun bind() {
|
||||
if (informationData.showInformation) {
|
||||
avatarImageView.visibility = View.VISIBLE
|
||||
memberNameView.visibility = View.VISIBLE
|
||||
timeView.visibility = View.VISIBLE
|
||||
timeView.text = informationData.time
|
||||
memberNameView.text = informationData.memberName
|
||||
AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView)
|
||||
} else {
|
||||
avatarImageView.visibility = View.GONE
|
||||
memberNameView.visibility = View.GONE
|
||||
timeView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
|
||||
class MessageImageItem(
|
||||
private val mediaData: MediaContentRenderer.Data,
|
||||
informationData: MessageInformationData
|
||||
) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) {
|
||||
|
||||
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
private val imageView by bind<ImageView>(R.id.messageImageView)
|
||||
|
||||
override fun bind() {
|
||||
super.bind()
|
||||
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.util.Linkify
|
||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
|
||||
class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val timelineDateFormatter: TimelineDateFormatter) {
|
||||
|
||||
private val messagesDisplayedWithInformation = HashSet<String?>()
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
callback: TimelineEventController.Callback?
|
||||
): AbsMessageItem? {
|
||||
|
||||
val roomMember = event.roomMember
|
||||
val nextRoomMember = nextEvent?.roomMember
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
|
||||
if (addDaySeparator
|
||||
|| nextRoomMember != roomMember
|
||||
|| nextEvent?.root?.type != EventType.MESSAGE
|
||||
|| isNextMessageReceivedMoreThanOneHourAgo) {
|
||||
messagesDisplayedWithInformation.add(event.root.eventId)
|
||||
}
|
||||
|
||||
val messageContent: MessageContent = event.root.content.toModel() ?: return null
|
||||
val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId)
|
||||
val time = timelineDateFormatter.formatMessageHour(date)
|
||||
val avatarUrl = roomMember?.avatarUrl
|
||||
val memberName = roomMember?.displayName ?: event.root.sender
|
||||
val informationData = MessageInformationData(time, avatarUrl, memberName, showInformation)
|
||||
|
||||
return when (messageContent) {
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildImageMessageItem(messageContent: MessageImageContent,
|
||||
informationData: MessageInformationData): MessageImageItem? {
|
||||
|
||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val data = MediaContentRenderer.Data(
|
||||
url = messageContent.url,
|
||||
height = messageContent.info?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.info?.width,
|
||||
maxWidth = maxWidth,
|
||||
rotation = messageContent.info?.rotation,
|
||||
orientation = messageContent.info?.orientation
|
||||
)
|
||||
return MessageImageItem(data, informationData)
|
||||
}
|
||||
|
||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
callback: TimelineEventController.Callback?): MessageTextItem? {
|
||||
|
||||
val message = messageContent.body.let {
|
||||
val spannable = SpannableStringBuilder(it)
|
||||
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
||||
override fun onUrlClicked(url: String) {
|
||||
callback?.onUrlClicked(url)
|
||||
}
|
||||
})
|
||||
Linkify.addLinks(spannable, Linkify.ALL)
|
||||
spannable
|
||||
}
|
||||
return MessageTextItem(
|
||||
message = message,
|
||||
informationData = informationData
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
class MessageTextItem(
|
||||
val message: CharSequence? = null,
|
||||
informationData: MessageInformationData
|
||||
) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_message) {
|
||||
|
||||
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
private val messageView by bind<TextView>(R.id.messageTextView)
|
||||
|
||||
override fun bind() {
|
||||
super.bind()
|
||||
messageView.text = message
|
||||
MatrixLinkify.addLinkMovementMethod(messageView)
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
class NoticeItem(private val noticeText: CharSequence? = null,
|
||||
private val avatarUrl: String?,
|
||||
private val memberName: CharSequence? = null)
|
||||
: KotlinModel(R.layout.item_timeline_event_notice) {
|
||||
|
||||
private val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||
private val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
||||
|
||||
override fun bind() {
|
||||
noticeTextView.text = noticeText
|
||||
AvatarRenderer.render(avatarUrl, memberName?.toString(), avatarImageView)
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2019 New Vector Ltd
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
class ReadReceiptsItem() : KotlinModel(R.layout.item_timeline_read_receipts) {
|
||||
|
||||
private val moreText by bind<TextView>(R.id.message_more_than_expected)
|
||||
private val avatarReceipt1 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt2 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt3 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt4 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt5 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipts = listOf(avatarReceipt1, avatarReceipt2, avatarReceipt3, avatarReceipt4, avatarReceipt5)
|
||||
|
||||
override fun bind() {
|
||||
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
|
||||
//TODO : complete with call membership events
|
||||
class RoomMemberItemFactory(private val stringProvider: StringProvider) {
|
||||
|
||||
fun create(event: TimelineEvent): NoticeItem? {
|
||||
val roomMember = event.roomMember ?: return null
|
||||
val noticeText = buildRoomMemberNotice(event) ?: return null
|
||||
return NoticeItem(noticeText, roomMember.avatarUrl, roomMember.displayName)
|
||||
}
|
||||
|
||||
private fun buildRoomMemberNotice(event: TimelineEvent): String? {
|
||||
val eventContent: RoomMember? = event.root.content.toModel()
|
||||
val prevEventContent: RoomMember? = event.root.prevContent.toModel()
|
||||
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
|
||||
return if (isMembershipEvent) {
|
||||
buildMembershipNotice(event, eventContent, prevEventContent)
|
||||
} else {
|
||||
buildProfileNotice(event, eventContent, prevEventContent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildProfileNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
|
||||
val displayText = StringBuilder()
|
||||
// Check display name has been changed
|
||||
if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) {
|
||||
val displayNameText = when {
|
||||
prevEventContent?.displayName.isNullOrEmpty() -> stringProvider.getString(R.string.notice_display_name_set, event.root.sender, eventContent?.displayName)
|
||||
eventContent?.displayName.isNullOrEmpty() -> stringProvider.getString(R.string.notice_display_name_removed, event.root.sender, prevEventContent?.displayName)
|
||||
else -> stringProvider.getString(R.string.notice_display_name_changed_from, event.root.sender, prevEventContent?.displayName, eventContent?.displayName)
|
||||
}
|
||||
displayText.append(displayNameText)
|
||||
}
|
||||
// Check whether the avatar has been changed
|
||||
if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) {
|
||||
val displayAvatarText = if (displayText.isNotEmpty()) {
|
||||
displayText.append(" ")
|
||||
stringProvider.getString(R.string.notice_avatar_changed_too)
|
||||
} else {
|
||||
stringProvider.getString(R.string.notice_avatar_url_changed, event.roomMember?.displayName)
|
||||
}
|
||||
displayText.append(displayAvatarText)
|
||||
}
|
||||
return displayText.toString()
|
||||
}
|
||||
|
||||
private fun buildMembershipNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
|
||||
val senderDisplayName = event.roomMember?.displayName ?: return null
|
||||
val targetDisplayName = eventContent?.displayName ?: event.root.sender
|
||||
return when {
|
||||
Membership.INVITE == eventContent?.membership -> {
|
||||
// TODO get userId
|
||||
val selfUserId: String = ""
|
||||
when {
|
||||
eventContent.thirdPartyInvite != null -> stringProvider.getString(R.string.notice_room_third_party_registered_invite, targetDisplayName, eventContent.thirdPartyInvite?.displayName)
|
||||
TextUtils.equals(event.root.stateKey, selfUserId)
|
||||
-> stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
|
||||
event.root.stateKey.isNullOrEmpty() -> stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName)
|
||||
else -> stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName)
|
||||
}
|
||||
}
|
||||
Membership.JOIN == eventContent?.membership -> stringProvider.getString(R.string.notice_room_join, senderDisplayName)
|
||||
Membership.LEAVE == eventContent?.membership -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
|
||||
return if (TextUtils.equals(event.root.sender, event.root.stateKey)) {
|
||||
if (prevEventContent?.membership == Membership.INVITE) {
|
||||
stringProvider.getString(R.string.notice_room_reject, senderDisplayName)
|
||||
} else {
|
||||
stringProvider.getString(R.string.notice_room_leave, senderDisplayName)
|
||||
}
|
||||
} else if (prevEventContent?.membership == Membership.INVITE) {
|
||||
stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName)
|
||||
} else if (prevEventContent?.membership == Membership.JOIN) {
|
||||
stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
||||
} else if (prevEventContent?.membership == Membership.BAN) {
|
||||
stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Membership.BAN == eventContent?.membership -> stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName)
|
||||
Membership.KNOCK == eventContent?.membership -> stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
class RoomNameItemFactory(private val stringProvider: StringProvider) {
|
||||
|
||||
fun create(event: TimelineEvent): NoticeItem? {
|
||||
|
||||
val content: RoomNameContent? = event.root.content.toModel()
|
||||
val roomMember = event.roomMember
|
||||
if (content == null || roomMember == null) {
|
||||
return null
|
||||
}
|
||||
val text = if (!TextUtils.isEmpty(content.name)) {
|
||||
stringProvider.getString(R.string.notice_room_name_changed, roomMember.displayName, content.name)
|
||||
} else {
|
||||
stringProvider.getString(R.string.notice_room_name_removed, roomMember.displayName)
|
||||
}
|
||||
return NoticeItem(text, roomMember.avatarUrl, roomMember.displayName)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
class RoomTopicItemFactory(private val stringProvider: StringProvider) {
|
||||
|
||||
fun create(event: TimelineEvent): NoticeItem? {
|
||||
|
||||
val content: RoomTopicContent? = event.root.content.toModel()
|
||||
val roomMember = event.roomMember
|
||||
if (content == null || roomMember == null) {
|
||||
return null
|
||||
}
|
||||
val text = if (content.topic.isNullOrEmpty()) {
|
||||
stringProvider.getString(R.string.notice_room_topic_removed, roomMember.displayName)
|
||||
} else {
|
||||
stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic)
|
||||
}
|
||||
return NoticeItem(text, roomMember.avatarUrl, roomMember.displayName)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||
import com.airbnb.epoxy.VisibilityState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineData
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.features.home.LoadingItemModel_
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
|
||||
|
||||
class TimelineEventController(private val roomId: String,
|
||||
private val dateFormatter: TimelineDateFormatter,
|
||||
private val timelineItemFactory: TimelineItemFactory,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider
|
||||
) : PagedListEpoxyController<TimelineEvent>(
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
) {
|
||||
init {
|
||||
setFilterDuplicates(true)
|
||||
}
|
||||
|
||||
private var isLoadingForward: Boolean = false
|
||||
private var isLoadingBackward: Boolean = false
|
||||
private var hasReachedEnd: Boolean = false
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
fun update(timelineData: TimelineData?) {
|
||||
timelineData?.let {
|
||||
isLoadingForward = it.isLoadingForward
|
||||
isLoadingBackward = it.isLoadingBackward
|
||||
hasReachedEnd = it.events.lastOrNull()?.root?.type == EventType.STATE_ROOM_CREATE
|
||||
submitList(it.events)
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView)
|
||||
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
override fun buildItemModels(currentPosition: Int, items: List<TimelineEvent?>): List<EpoxyModel<*>> {
|
||||
if (items.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val epoxyModels = ArrayList<EpoxyModel<*>>()
|
||||
val event = items[currentPosition] ?: return emptyList()
|
||||
val nextEvent = if (currentPosition + 1 < items.size) items[currentPosition + 1] else null
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
|
||||
timelineItemFactory.create(event, nextEvent, callback)?.also {
|
||||
it.id(event.localId)
|
||||
it.setOnVisibilityStateChanged(OnModelVisibilityStateChangedListener<KotlinModel, View> { model, view, visibilityState ->
|
||||
if (visibilityState == VisibilityState.VISIBLE) {
|
||||
callback?.onEventVisible(event, currentPosition)
|
||||
}
|
||||
})
|
||||
epoxyModels.add(it)
|
||||
}
|
||||
if (addDaySeparator) {
|
||||
val formattedDay = dateFormatter.formatMessageDay(date)
|
||||
val daySeparatorItem = DaySeparatorItem(formattedDay).id(roomId + formattedDay)
|
||||
epoxyModels.add(daySeparatorItem)
|
||||
}
|
||||
return epoxyModels
|
||||
}
|
||||
|
||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||
LoadingItemModel_()
|
||||
.id(roomId + "forward_loading_item")
|
||||
.addIf(isLoadingForward, this)
|
||||
|
||||
super.add(models)
|
||||
|
||||
LoadingItemModel_()
|
||||
.id(roomId + "backward_loading_item")
|
||||
.addIf(!hasReachedEnd, this)
|
||||
}
|
||||
|
||||
|
||||
interface Callback {
|
||||
fun onEventVisible(event: TimelineEvent, index: Int)
|
||||
fun onUrlClicked(url: String)
|
||||
}
|
||||
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||
private val roomNameItemFactory: RoomNameItemFactory,
|
||||
private val roomTopicItemFactory: RoomTopicItemFactory,
|
||||
private val roomMemberItemFactory: RoomMemberItemFactory,
|
||||
private val defaultItemFactory: DefaultItemFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
callback: TimelineEventController.Callback?): KotlinModel? {
|
||||
|
||||
return when (event.root.type) {
|
||||
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback)
|
||||
EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event)
|
||||
EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event)
|
||||
EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event)
|
||||
else -> defaultItemFactory.create(event)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline.paging
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import android.os.Handler
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.EpoxyViewHolder
|
||||
|
||||
/**
|
||||
* An [EpoxyController] that can work with a [PagedList].
|
||||
*
|
||||
* Internally, it caches the model for each item in the [PagedList]. You should override
|
||||
* [buildItemModel] method to build the model for the given item. Since [PagedList] might include
|
||||
* `null` items if placeholders are enabled, this method needs to handle `null` values in the list.
|
||||
*
|
||||
* By default, the model for each item is added to the model list. To change this behavior (to
|
||||
* filter items or inject extra items), you can override [addModels] function and manually add built
|
||||
* models.
|
||||
*
|
||||
* @param T The type of the items in the [PagedList].
|
||||
*/
|
||||
abstract class PagedListEpoxyController<T>(
|
||||
/**
|
||||
* The handler to use for building models. By default this uses the main thread, but you can use
|
||||
* [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do model building in the background.
|
||||
*
|
||||
* The notify thread of your PagedList (from setNotifyExecutor in the PagedList Builder) must be
|
||||
* the same as this thread. Otherwise Epoxy will crash.
|
||||
*/
|
||||
modelBuildingHandler: Handler = EpoxyController.defaultModelBuildingHandler,
|
||||
/**
|
||||
* The handler to use when calculating the diff between built model lists.
|
||||
* By default this uses the main thread, but you can use
|
||||
* [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do diffing in the background.
|
||||
*/
|
||||
diffingHandler: Handler = EpoxyController.defaultDiffingHandler,
|
||||
/**
|
||||
* [PagedListEpoxyController] uses an [DiffUtil.ItemCallback] to detect changes between
|
||||
* [PagedList]s. By default, it relies on simple object equality but you can provide a custom
|
||||
* one if you don't use all fields in the object in your models.
|
||||
*/
|
||||
itemDiffCallback: DiffUtil.ItemCallback<T> = DEFAULT_ITEM_DIFF_CALLBACK as DiffUtil.ItemCallback<T>
|
||||
) : EpoxyController(modelBuildingHandler, diffingHandler) {
|
||||
// this is where we keep the already built models
|
||||
protected val modelCache = PagedListModelCache(
|
||||
modelBuilder = { pos, item ->
|
||||
buildItemModels(pos, item)
|
||||
},
|
||||
rebuildCallback = {
|
||||
requestModelBuild()
|
||||
},
|
||||
itemDiffCallback = itemDiffCallback,
|
||||
modelBuildingHandler = modelBuildingHandler
|
||||
)
|
||||
|
||||
var currentList: PagedList<T>? = null
|
||||
private set
|
||||
|
||||
final override fun buildModels() {
|
||||
addModels(modelCache.getModels())
|
||||
}
|
||||
|
||||
override fun onModelBound(
|
||||
holder: EpoxyViewHolder,
|
||||
boundModel: EpoxyModel<*>,
|
||||
position: Int,
|
||||
previouslyBoundModel: EpoxyModel<*>?
|
||||
) {
|
||||
modelCache.loadAround(boundModel)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds all built models to the adapter. You can override this method to add extra
|
||||
* items into the model list or remove some.
|
||||
*/
|
||||
open fun addModels(models: List<EpoxyModel<*>>) {
|
||||
super.add(models)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the model for a given item. This must return a single model for each item. If you want
|
||||
* to inject headers etc, you can override [addModels] function.
|
||||
*
|
||||
* If the `item` is `null`, you should provide the placeholder. If your [PagedList] is configured
|
||||
* without placeholders, you don't need to handle the `null` case.
|
||||
*/
|
||||
abstract fun buildItemModels(currentPosition: Int, items: List<T?>): List<EpoxyModel<*>>
|
||||
|
||||
/**
|
||||
* Submit a new paged list.
|
||||
*
|
||||
* A diff will be calculated between this list and the previous list so you may still get calls
|
||||
* to [buildItemModel] with items from the previous list.
|
||||
*/
|
||||
fun submitList(newList: PagedList<T>?) {
|
||||
currentList = newList
|
||||
modelCache.submitList(newList)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* [PagedListEpoxyController] calculates a diff on top of the PagedList to check which
|
||||
* models are invalidated.
|
||||
* This is the default [DiffUtil.ItemCallback] which uses object equality.
|
||||
*/
|
||||
val DEFAULT_ITEM_DIFF_CALLBACK = object : DiffUtil.ItemCallback<Any>() {
|
||||
override fun areItemsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem
|
||||
|
||||
override fun areContentsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.detail.timeline.paging
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import androidx.paging.AsyncPagedListDiffer
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* A PagedList stream wrapper that caches models built for each item. It tracks changes in paged lists and caches
|
||||
* models for each item when they are invalidated to avoid rebuilding models for the whole list when PagedList is
|
||||
* updated.
|
||||
*/
|
||||
class PagedListModelCache<T>(
|
||||
private val modelBuilder: (itemIndex: Int, items: List<T>) -> List<EpoxyModel<*>>,
|
||||
private val rebuildCallback: () -> Unit,
|
||||
private val itemDiffCallback: DiffUtil.ItemCallback<T>,
|
||||
private val diffExecutor: Executor? = null,
|
||||
private val modelBuildingHandler: Handler
|
||||
) {
|
||||
|
||||
|
||||
// Int is the index of the pagedList item
|
||||
// We have to be able to find the pagedlist position coming from an epoxy model to trigger
|
||||
// LoadAround with accuracy
|
||||
private val modelCache = linkedMapOf<EpoxyModel<*>, Int>()
|
||||
private var isCacheStale = AtomicBoolean(true)
|
||||
|
||||
/**
|
||||
* Tracks the last accessed position so that we can report it back to the paged list when models are built.
|
||||
*/
|
||||
private var lastPosition: Int? = null
|
||||
|
||||
/**
|
||||
* Observer for the PagedList changes that invalidates the model cache when data is updated.
|
||||
*/
|
||||
private val updateCallback = object : ListUpdateCallback {
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private val asyncDiffer = AsyncPagedListDiffer<T>(
|
||||
updateCallback,
|
||||
AsyncDifferConfig.Builder<T>(
|
||||
itemDiffCallback
|
||||
).also { builder ->
|
||||
if (diffExecutor != null) {
|
||||
builder.setBackgroundThreadExecutor(diffExecutor)
|
||||
}
|
||||
// we have to reply on this private API, otherwise, paged list might be changed when models are being built,
|
||||
// potentially creating concurrent modification problems.
|
||||
builder.setMainThreadExecutor { runnable: Runnable ->
|
||||
modelBuildingHandler.post(runnable)
|
||||
}
|
||||
}.build()
|
||||
)
|
||||
|
||||
fun submitList(pagedList: PagedList<T>?) {
|
||||
asyncDiffer.submitList(pagedList)
|
||||
}
|
||||
|
||||
fun getModels(): List<EpoxyModel<*>> {
|
||||
if (isCacheStale.compareAndSet(true, false)) {
|
||||
asyncDiffer.currentList?.forEachIndexed { position, _ ->
|
||||
buildModel(position)
|
||||
}
|
||||
}
|
||||
lastPosition?.let {
|
||||
triggerLoadAround(it)
|
||||
}
|
||||
return modelCache.keys.toList()
|
||||
}
|
||||
|
||||
fun loadAround(model: EpoxyModel<*>) {
|
||||
modelCache[model]?.let { itemPosition ->
|
||||
triggerLoadAround(itemPosition)
|
||||
lastPosition = itemPosition
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun invalidate() {
|
||||
modelCache.clear()
|
||||
isCacheStale.set(true)
|
||||
}
|
||||
|
||||
private fun cacheModelsAtPosition(itemPosition: Int, epoxyModels: Set<EpoxyModel<*>>) {
|
||||
epoxyModels.forEach {
|
||||
modelCache[it] = itemPosition
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildModel(pos: Int) {
|
||||
if (pos >= asyncDiffer.currentList?.size ?: 0) {
|
||||
return
|
||||
}
|
||||
modelBuilder(pos, asyncDiffer.currentList as List<T>).also {
|
||||
cacheModelsAtPosition(pos, it.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerLoadAround(position: Int) {
|
||||
asyncDiffer.currentList?.let {
|
||||
if (it.size > 0) {
|
||||
it.loadAround(Math.min(position, it.size - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
data class RoomCategoryItem(
|
||||
val title: CharSequence,
|
||||
val isExpanded: Boolean,
|
||||
val unreadCount: Int,
|
||||
val showHighlighted: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_room_category) {
|
||||
|
||||
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomCategoryUnreadCounterBadgeView)
|
||||
private val titleView by bind<TextView>(R.id.roomCategoryTitleView)
|
||||
private val rootView by bind<ViewGroup>(R.id.roomCategoryRootView)
|
||||
|
||||
private val tintColor by lazy {
|
||||
ContextCompat.getColor(rootView.context, R.color.bluey_grey_two)
|
||||
}
|
||||
|
||||
override fun bind() {
|
||||
val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
||||
val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
}
|
||||
unreadCounterBadgeView.render(unreadCount, showHighlighted)
|
||||
titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
|
||||
titleView.text = title
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.setupAsSearch
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import im.vector.riotredesign.core.platform.StateView
|
||||
import im.vector.riotredesign.features.home.HomeNavigator
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
|
||||
|
||||
companion object {
|
||||
fun newInstance(): RoomListFragment {
|
||||
return RoomListFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private val homeNavigator by inject<HomeNavigator>()
|
||||
private val roomController by inject<RoomSummaryController>()
|
||||
private val homeViewModel: RoomListViewModel by activityViewModel()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_room_list, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
roomController.callback = this
|
||||
stateView.contentView = epoxyRecyclerView
|
||||
epoxyRecyclerView.setController(roomController)
|
||||
setupFilterView()
|
||||
homeViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
private fun renderState(state: RoomListViewState) {
|
||||
when (state.asyncRooms) {
|
||||
is Incomplete -> renderLoading()
|
||||
is Success -> renderSuccess(state)
|
||||
is Fail -> renderFailure(state.asyncRooms.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuccess(state: RoomListViewState) {
|
||||
if (state.asyncRooms().isNullOrEmpty()) {
|
||||
stateView.state = StateView.State.Empty(getString(R.string.room_list_empty))
|
||||
} else {
|
||||
stateView.state = StateView.State.Content
|
||||
}
|
||||
roomController.setData(state)
|
||||
}
|
||||
|
||||
private fun renderLoading() {
|
||||
stateView.state = StateView.State.Loading
|
||||
}
|
||||
|
||||
private fun renderFailure(error: Throwable) {
|
||||
val message = when (error) {
|
||||
is Failure.NetworkConnection -> getString(R.string.error_no_network)
|
||||
else -> getString(R.string.error_common)
|
||||
}
|
||||
stateView.state = StateView.State.Error(message)
|
||||
}
|
||||
|
||||
private fun setupFilterView() {
|
||||
filterRoomView.setupAsSearch()
|
||||
filterRoomView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) = Unit
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
homeViewModel.accept(RoomListActions.FilterRooms(s))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// RoomSummaryController.Callback **************************************************************
|
||||
|
||||
override fun onRoomSelected(room: RoomSummary) {
|
||||
homeViewModel.accept(RoomListActions.SelectRoom(room))
|
||||
homeNavigator.openRoomDetail(room.roomId, null)
|
||||
}
|
||||
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Function3
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.koin.android.ext.android.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
typealias RoomListFilterName = CharSequence
|
||||
|
||||
class RoomListViewModel(initialState: RoomListViewState,
|
||||
private val session: Session,
|
||||
private val selectedGroupHolder: SelectedGroupHolder,
|
||||
private val visibleRoomHolder: VisibleRoomHolder,
|
||||
private val roomSelectionRepository: RoomSelectionRepository,
|
||||
private val roomSummaryComparator: RoomSummaryComparator)
|
||||
: RiotViewModel<RoomListViewState>(initialState) {
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? {
|
||||
val currentSession = Matrix.getInstance().currentSession
|
||||
val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>()
|
||||
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupHolder>()
|
||||
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomHolder>()
|
||||
val roomSummaryComparator = viewModelContext.activity.get<RoomSummaryComparator>()
|
||||
return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository, roomSummaryComparator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val roomListFilter = BehaviorRelay.createDefault<Option<RoomListFilterName>>(Option.empty())
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
observeVisibleRoom()
|
||||
}
|
||||
|
||||
fun accept(action: RoomListActions) {
|
||||
when (action) {
|
||||
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
||||
is RoomListActions.FilterRooms -> handleFilterRooms(action)
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state ->
|
||||
if (state.selectedRoomId != action.roomSummary.roomId) {
|
||||
roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFilterRooms(action: RoomListActions.FilterRooms) {
|
||||
val optionalFilter = Option.fromNullable(action.roomName)
|
||||
roomListFilter.accept(optionalFilter)
|
||||
}
|
||||
|
||||
private fun observeVisibleRoom() {
|
||||
visibleRoomHolder.visibleRoom()
|
||||
.subscribeBy {
|
||||
setState { copy(selectedRoomId = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, Option<RoomListFilterName>, RoomSummaries>(
|
||||
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS),
|
||||
selectedGroupHolder.selectedGroup(),
|
||||
roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||
Function3 { rooms, selectedGroupOption, filterRoomOption ->
|
||||
val filteredRooms = filterRooms(rooms, filterRoomOption)
|
||||
val selectedGroup = selectedGroupOption.orNull()
|
||||
val filteredDirectRooms = filteredRooms
|
||||
.filter { it.isDirect }
|
||||
.filter {
|
||||
if (selectedGroup == null) {
|
||||
true
|
||||
} else {
|
||||
it.otherMemberIds
|
||||
.intersect(selectedGroup.userIds)
|
||||
.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
val filteredGroupRooms = filteredRooms
|
||||
.filter { !it.isDirect }
|
||||
.filter {
|
||||
selectedGroup?.roomIds?.contains(it.roomId) ?: true
|
||||
}
|
||||
buildRoomSummaries(filteredDirectRooms + filteredGroupRooms)
|
||||
}
|
||||
)
|
||||
.execute { async ->
|
||||
copy(
|
||||
asyncRooms = async
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterRooms(rooms: List<RoomSummary>, filterRoomOption: Option<RoomListFilterName>): List<RoomSummary> {
|
||||
val filterRoom = filterRoomOption.orNull()
|
||||
return rooms.filter {
|
||||
if (filterRoom.isNullOrBlank()) {
|
||||
true
|
||||
} else {
|
||||
it.displayName.contains(other = filterRoom, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
val directChats = ArrayList<RoomSummary>()
|
||||
val groupRooms = ArrayList<RoomSummary>()
|
||||
val lowPriorities = ArrayList<RoomSummary>()
|
||||
val serverNotices = ArrayList<RoomSummary>()
|
||||
|
||||
for (room in rooms) {
|
||||
val tags = room.tags.map { it.name }
|
||||
when {
|
||||
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
|
||||
room.isDirect -> directChats.add(room)
|
||||
else -> groupRooms.add(room)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomSummaries(
|
||||
favourites = favourites.sortedWith(roomSummaryComparator),
|
||||
directRooms = directChats.sortedWith(roomSummaryComparator),
|
||||
groupRooms = groupRooms.sortedWith(roomSummaryComparator),
|
||||
lowPriorities = lowPriorities.sortedWith(roomSummaryComparator),
|
||||
serverNotices = serverNotices.sortedWith(roomSummaryComparator)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
||||
data class RoomListViewState(
|
||||
val asyncRooms: Async<RoomSummaries> = Uninitialized,
|
||||
val selectedRoomId: String? = null
|
||||
) : MvRxState
|
||||
|
||||
data class RoomSummaries(
|
||||
val favourites: List<RoomSummary>,
|
||||
val directRooms: List<RoomSummary>,
|
||||
val groupRooms: List<RoomSummary>,
|
||||
val lowPriorities: List<RoomSummary>,
|
||||
val serverNotices: List<RoomSummary>
|
||||
)
|
||||
|
||||
fun RoomSummaries?.isNullOrEmpty(): Boolean {
|
||||
return this == null || (directRooms.isEmpty() && groupRooms.isEmpty() && favourites.isEmpty() && lowPriorities.isEmpty() && serverNotices.isEmpty())
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
||||
class RoomSummaryComparator
|
||||
: Comparator<RoomSummary> {
|
||||
|
||||
override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
|
||||
val retValue: Int
|
||||
var leftHighlightCount = 0
|
||||
var rightHighlightCount = 0
|
||||
var leftNotificationCount = 0
|
||||
var rightNotificationCount = 0
|
||||
var rightTimestamp = 0L
|
||||
var leftTimestamp = 0L
|
||||
|
||||
if (null != leftRoomSummary) {
|
||||
leftHighlightCount = leftRoomSummary.highlightCount
|
||||
leftNotificationCount = leftRoomSummary.notificationCount
|
||||
leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0
|
||||
}
|
||||
if (null != rightRoomSummary) {
|
||||
rightHighlightCount = rightRoomSummary.highlightCount
|
||||
rightNotificationCount = rightRoomSummary.notificationCount
|
||||
rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0
|
||||
}
|
||||
|
||||
if (leftRoomSummary?.lastMessage == null) {
|
||||
retValue = 1
|
||||
} else if (rightRoomSummary?.lastMessage == null) {
|
||||
retValue = -1
|
||||
} else if (rightHighlightCount > 0 && leftHighlightCount == 0) {
|
||||
retValue = 1
|
||||
} else if (rightHighlightCount == 0 && leftHighlightCount > 0) {
|
||||
retValue = -1
|
||||
} else if (rightNotificationCount > 0 && leftNotificationCount == 0) {
|
||||
retValue = 1
|
||||
} else if (rightNotificationCount == 0 && leftNotificationCount > 0) {
|
||||
retValue = -1
|
||||
} else {
|
||||
val deltaTimestamp = rightTimestamp - leftTimestamp
|
||||
if (deltaTimestamp > 0) {
|
||||
retValue = 1
|
||||
} else if (deltaTimestamp < 0) {
|
||||
retValue = -1
|
||||
} else {
|
||||
retValue = 0
|
||||
}
|
||||
}
|
||||
return retValue
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
class RoomSummaryController(private val stringProvider: StringProvider
|
||||
) : TypedEpoxyController<RoomListViewState>() {
|
||||
|
||||
private var isFavoriteRoomsExpanded = true
|
||||
private var isDirectRoomsExpanded = false
|
||||
private var isGroupRoomsExpanded = false
|
||||
private var isLowPriorityRoomsExpanded = false
|
||||
private var isServerNoticeRoomsExpanded = false
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun buildModels(viewState: RoomListViewState) {
|
||||
val roomSummaries = viewState.asyncRooms()
|
||||
val favourites = roomSummaries?.favourites ?: emptyList()
|
||||
buildRoomCategory(viewState, favourites, R.string.room_list_favourites, isFavoriteRoomsExpanded) {
|
||||
isFavoriteRoomsExpanded = !isFavoriteRoomsExpanded
|
||||
}
|
||||
if (isFavoriteRoomsExpanded) {
|
||||
buildRoomModels(favourites, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val directRooms = roomSummaries?.directRooms ?: emptyList()
|
||||
buildRoomCategory(viewState, directRooms, R.string.room_list_direct, isDirectRoomsExpanded) {
|
||||
isDirectRoomsExpanded = !isDirectRoomsExpanded
|
||||
}
|
||||
if (isDirectRoomsExpanded) {
|
||||
buildRoomModels(directRooms, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val groupRooms = roomSummaries?.groupRooms ?: emptyList()
|
||||
buildRoomCategory(viewState, groupRooms, R.string.room_list_group, isGroupRoomsExpanded) {
|
||||
isGroupRoomsExpanded = !isGroupRoomsExpanded
|
||||
}
|
||||
if (isGroupRoomsExpanded) {
|
||||
buildRoomModels(groupRooms, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val lowPriorities = roomSummaries?.lowPriorities ?: emptyList()
|
||||
buildRoomCategory(viewState, lowPriorities, R.string.room_list_low_priority, isLowPriorityRoomsExpanded) {
|
||||
isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded
|
||||
}
|
||||
if (isLowPriorityRoomsExpanded) {
|
||||
buildRoomModels(lowPriorities, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val serverNotices = roomSummaries?.serverNotices ?: emptyList()
|
||||
buildRoomCategory(viewState, serverNotices, R.string.room_list_system_alert, isServerNoticeRoomsExpanded) {
|
||||
isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded
|
||||
}
|
||||
if (isServerNoticeRoomsExpanded) {
|
||||
buildRoomModels(serverNotices, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun buildRoomCategory(viewState: RoomListViewState, summaries: List<RoomSummary>, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) {
|
||||
//TODO should add some business logic later
|
||||
val unreadCount = if (summaries.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
summaries.map { it.notificationCount }.reduce { acc, i -> acc + i }
|
||||
}
|
||||
val showHighlighted = summaries.any { it.highlightCount > 0 }
|
||||
RoomCategoryItem(
|
||||
title = stringProvider.getString(titleRes).toUpperCase(),
|
||||
isExpanded = isExpanded,
|
||||
unreadCount = unreadCount,
|
||||
showHighlighted = showHighlighted,
|
||||
listener = {
|
||||
mutateExpandedState()
|
||||
setData(viewState)
|
||||
}
|
||||
)
|
||||
.id(titleRes)
|
||||
.addTo(this)
|
||||
}
|
||||
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>, selectedRoomId: String?) {
|
||||
summaries.forEach { roomSummary ->
|
||||
val unreadCount = roomSummary.notificationCount
|
||||
val showHighlighted = roomSummary.highlightCount > 0
|
||||
val isSelected = roomSummary.roomId == selectedRoomId
|
||||
RoomSummaryItem(
|
||||
roomName = roomSummary.displayName,
|
||||
avatarUrl = roomSummary.avatarUrl,
|
||||
isSelected = isSelected,
|
||||
showHighlighted = showHighlighted,
|
||||
unreadCount = unreadCount,
|
||||
listener = { callback?.onRoomSelected(roomSummary) }
|
||||
)
|
||||
.id(roomSummary.roomId)
|
||||
.addTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onRoomSelected(room: RoomSummary)
|
||||
}
|
||||
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.room.list
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.platform.CheckableFrameLayout
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
|
||||
data class RoomSummaryItem(
|
||||
val roomName: CharSequence,
|
||||
val avatarUrl: String?,
|
||||
val isSelected: Boolean,
|
||||
val unreadCount: Int,
|
||||
val showHighlighted: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_room) {
|
||||
|
||||
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
|
||||
private val titleView by bind<TextView>(R.id.roomNameView)
|
||||
private val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||
private val rootView by bind<CheckableFrameLayout>(R.id.itemRoomLayout)
|
||||
|
||||
override fun bind() {
|
||||
unreadCounterBadgeView.render(unreadCount, showHighlighted)
|
||||
rootView.isChecked = isSelected
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
titleView.text = roomName
|
||||
AvatarRenderer.render(avatarUrl, roomName.toString(), avatarImageView)
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/black" android:state_checked="true" />
|
||||
<item android:color="@color/bluey_grey_two" />
|
||||
|
||||
</selector>
|
Before Width: | Height: | Size: 335 B |
Before Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 41 KiB |
@ -1,34 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
Before Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 550 B |
Before Width: | Height: | Size: 728 B |
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:opacity="opaque">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:gravity="top">
|
||||
<shape android:shape="rectangle">
|
||||
<size android:height="80dp" />
|
||||
<solid android:color="?colorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke android:width="3dp" android:color="@color/pale_teal" />
|
||||
</shape>
|
||||
|
||||
</item>
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
|
||||
</selector>
|
@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
Before Width: | Height: | Size: 30 KiB |
@ -1,96 +0,0 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/logoImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="100dp"
|
||||
android:src="@drawable/logo_login" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Username">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/loginField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textEmailAddress"
|
||||
android:maxLines="1" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Password">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/passwordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Homeserver url">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/homeServerField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:maxLines="1" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/authenticateButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/authenticateButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="Authenticate"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<im.vector.riotredesign.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/stateView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/dark">
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxyRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</im.vector.riotredesign.core.platform.StateView>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/stateView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/groupListFragmentContainer"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/roomListFragmentContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/groupListFragmentContainer"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/pale_grey">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/filterRoomView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_search_edit_text"
|
||||
android:drawableLeft="@drawable/ic_search_white"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableTint="#9fa9ba"
|
||||
android:hint="Filter by name..."
|
||||
android:lines="1"
|
||||
android:paddingLeft="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stateView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotredesign.core.platform.StateView
|
||||
android:id="@+id/stateView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/filterRoomView">
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxyRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</im.vector.riotredesign.core.platform.StateView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<im.vector.riotredesign.core.platform.CheckableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemGroupLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/groupAvatarImageView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:duplicateParentState="true"
|
||||
android:foreground="@drawable/fg_group_item"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
</im.vector.riotredesign.core.platform.CheckableFrameLayout>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:padding="16dp" />
|
@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<im.vector.riotredesign.core.platform.CheckableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemRoomLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_room_item"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:duplicateParentState="true"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roomAvatarImageView"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:duplicateParentState="true"
|
||||
android:textColor="@color/color_room_title"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
|
||||
app:layout_constraintStart_toEndOf="@id/roomAvatarImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
|
||||
android:id="@+id/roomUnreadCounterBadgeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minWidth="24dp"
|
||||
android:minHeight="24dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@drawable/bg_unread_highlight"
|
||||
tools:text="115" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</im.vector.riotredesign.core.platform.CheckableFrameLayout>
|
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/roomCategoryRootView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
tools:background="@color/pale_grey">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomCategoryTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawableLeft="@drawable/ic_expand_more_white"
|
||||
android:drawableTint="@color/bluey_grey_two"
|
||||
android:gravity="center_vertical"
|
||||
android:text="DIRECT MESSAGES"
|
||||
android:textColor="@color/bluey_grey_two"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/roomCategoryUnreadCounterBadgeView"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
|
||||
android:id="@+id/roomCategoryUnreadCounterBadgeView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minWidth="24dp"
|
||||
android:minHeight="24dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/roomCategoryAddButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@drawable/bg_unread_highlight"
|
||||
tools:text="4" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roomCategoryAddButton"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_add_circle_white"
|
||||
android:tint="@color/bluey_grey_two"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageMemberNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTimeView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/brown_grey"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageImageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemNoticeAvatarView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemNoticeTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="@color/slate_grey"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/itemNoticeAvatarView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Mon item" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageMemberNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTimeView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/brown_grey"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="@color/dark_grey"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView"
|
||||
tools:text="Alright finished work, heading there in about 20 mins…
Ping me when you’re outside" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/read_receipt_avatars_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_more_than_expected"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textSize="9sp"
|
||||
tools:text="999+" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_5"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_4"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_3"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_2"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_1"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="40dp"
|
||||
android:padding="8dp"
|
||||
tools:parentTag="android.widget.FrameLayout">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/errorView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorMessageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp"
|
||||
tools:text="Une erreur est survenue" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/errorRetryView"
|
||||
android:layout_width="190dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/global_retry"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/emptyView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyMessageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emptyImageView"
|
||||
android:layout_width="190dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Base.V21.Theme.Riot" parent="Base.V1.Theme.Riot"></style>
|
||||
|
||||
<style name="Base.Theme.Riot" parent="Base.V21.Theme.Riot" />
|
||||
|
||||
|
||||
</resources>
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="pale_grey">#f2f5f8</color>
|
||||
<color name="dark">#2e3649</color>
|
||||
<color name="pale_teal">#7ac9a1</color>
|
||||
<color name="black">#212121</color>
|
||||
<color name="deep_sky_blue">#007aff</color>
|
||||
<color name="rosy_pink">#f56679</color>
|
||||
<color name="bluey_grey">#a5a5a6</color>
|
||||
<color name="slate_grey">#5f6268</color>
|
||||
<color name="sky_blue">#7bb2ea</color>
|
||||
<color name="bluey_grey_two">#929eb4</color>
|
||||
<color name="light_blue_grey">#4ac1c9d6</color>
|
||||
<color name="dark_grey">#2e2f32</color>
|
||||
<color name="light_grey_blue">#9fa9ba</color>
|
||||
<color name="cool_grey">#a5aab2</color>
|
||||
<color name="pale_grey_two">#ebedf8</color>
|
||||
<color name="brown_grey">#a5a5a5</color>
|
||||
<color name="grey_lynch">#61708B</color>
|
||||
</resources>
|
@ -1,15 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">"Riot X"</string>
|
||||
|
||||
<string name="global_retry">"Retry"</string>
|
||||
<string name="error_no_network">"No network connection"</string>
|
||||
<string name="error_common">"An error occurred"</string>
|
||||
|
||||
<string name="room_list_empty">"Join a room to start using the app."</string>
|
||||
<string name="room_list_favourites">"Favourites"</string>
|
||||
<string name="room_list_direct">"People"</string>
|
||||
<string name="room_list_group">"Rooms"</string>
|
||||
<string name="room_list_low_priority">"Low priority"</string>
|
||||
<string name="room_list_system_alert">"System Alerts"</string>
|
||||
|
||||
</resources>
|
@ -1,4 +0,0 @@
|
||||
<resources>
|
||||
|
||||
|
||||
</resources>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Widget.Button" parent="Widget.AppCompat.Button">
|
||||
<item name="android:minHeight">48dp</item>
|
||||
<item name="android:background">?attr/colorAccent</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Riot" parent="Base.Theme.Riot">
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Riot.Splash">
|
||||
<item name="android:windowBackground">@drawable/bg_splash</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Base.V1.Theme.Riot" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/dark</item>
|
||||
<item name="colorPrimaryDark">@color/dark</item>
|
||||
<item name="colorAccent">@color/pale_teal</item>
|
||||
<item name="buttonStyle">@style/Widget.Button</item>
|
||||
</style>
|
||||
|
||||
<style name="Base.Theme.Riot" parent="Base.V1.Theme.Riot" />
|
||||
|
||||
|
||||
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|