Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fcfd28cd7 | ||
|
|
6438787134 | ||
|
|
199eb7db9a | ||
|
|
7b3e84826a | ||
|
|
e3d3263eb9 | ||
|
|
7fa9c6fd78 | ||
|
|
9c844ac9ca | ||
|
|
a44e9a847f | ||
|
|
b6da3d1ab6 | ||
|
|
3fdff255c8 | ||
|
|
e510870672 | ||
|
|
46078ca5e3 | ||
|
|
dee309370d | ||
|
|
af177b4c6d | ||
|
|
44174be084 | ||
|
|
bf22f28f75 | ||
|
|
a11a2ae397 | ||
|
|
99637b1a36 | ||
|
|
40b67b3f10 | ||
|
|
95c90808eb | ||
|
|
64c2aa0d12 | ||
|
|
7856e5523e | ||
|
|
3e59d4fb88 | ||
|
|
9ecdb6da75 | ||
|
|
31149f9d1e | ||
|
|
e28ab66159 | ||
|
|
d85b87e45d | ||
|
|
4b44fd5905 | ||
|
|
3ac26f1387 | ||
|
|
bceac49697 | ||
|
|
8866f306d6 | ||
|
|
e6f3ecbc7e | ||
|
|
aceb489374 | ||
|
|
9f6ac297ce | ||
|
|
f70839d406 | ||
|
|
855427376a | ||
|
|
38c1337c39 | ||
|
|
7da1540913 | ||
|
|
6dbe06a941 | ||
|
|
a27ba17521 | ||
|
|
354bd3468e | ||
|
|
0b2aa3edab | ||
|
|
f3245b3c20 | ||
|
|
91b4f0e75d | ||
|
|
cba7cc879c | ||
|
|
d2c5d00907 | ||
|
|
afb9e36bca | ||
|
|
22583d08e7 | ||
|
|
5c1b0caac5 | ||
|
|
2045521a97 | ||
|
|
95e4ba0f93 | ||
|
|
41c2ae9289 | ||
|
|
aa1a7a1c38 | ||
|
|
b75898f0c1 | ||
|
|
2e299ebef3 | ||
|
|
21d5e097f6 | ||
|
|
6f8b7244c4 | ||
|
|
d305ae7fa3 | ||
|
|
8059291f71 | ||
|
|
b2dff44df1 | ||
|
|
ffe160a6e7 | ||
|
|
248d62ebf7 | ||
|
|
575bf6905f | ||
|
|
0c342fe733 | ||
|
|
e67fa4ac75 | ||
|
|
b6309ac9a9 | ||
|
|
33b2b02267 | ||
|
|
6cfc723dfc | ||
|
|
5934465197 | ||
|
|
30d4674389 | ||
|
|
54ab886ef4 | ||
|
|
68f3d73f27 | ||
|
|
d3c9487ba8 | ||
|
|
73ec2c5fa2 | ||
|
|
f457473399 | ||
|
|
4f5555a2cd | ||
|
|
a5eabd5cec | ||
|
|
b085ca4d3b | ||
|
|
6766b10f29 | ||
|
|
ec887d4a16 | ||
|
|
a66a6953fc | ||
|
|
155f5b0b81 | ||
|
|
5fb6ea7799 | ||
|
|
3e39e20788 | ||
|
|
f5f33ed0e8 | ||
|
|
41524ceb4c | ||
|
|
a3a081b17d | ||
|
|
d07255dd3b | ||
|
|
8092fb8c02 | ||
|
|
542b469bda | ||
|
|
f102a6dd79 | ||
|
|
ccef9eff91 | ||
|
|
cb2d0d7edd | ||
|
|
68b540a635 | ||
|
|
541fffbe29 | ||
|
|
c11beffb5a | ||
|
|
bf5559fa8c | ||
|
|
8a4e232a3b | ||
|
|
b2b574a6ed | ||
|
|
bf18356bdf | ||
|
|
0961e859c5 | ||
|
|
785f22f039 | ||
|
|
e1fe4e4994 | ||
|
|
8a5e40f91d | ||
|
|
2f938d5f6a | ||
|
|
68c86390e2 | ||
|
|
b576942bd4 | ||
|
|
cca75dca9b | ||
|
|
6e5e9ffc3c | ||
|
|
38df8bc316 | ||
|
|
a498e48470 | ||
|
|
5ea80d9bdd | ||
|
|
51565edf35 | ||
|
|
c11be1123e | ||
|
|
89b1edc565 | ||
|
|
51b1a9517c | ||
|
|
6ae82b73c0 | ||
|
|
7e6cd9d996 | ||
|
|
dd5886f6d8 | ||
|
|
c5ba588cc3 | ||
|
|
b8b9e61378 | ||
|
|
31ca07feb0 | ||
|
|
3fd4a48208 | ||
|
|
ba36426905 | ||
|
|
ffdaca06a6 | ||
|
|
b682fa0c43 | ||
|
|
f9f9b59415 | ||
|
|
46a911cef1 | ||
|
|
6539de70f6 | ||
|
|
7a5389fdd1 | ||
|
|
76974e43d7 | ||
|
|
97c259a7b6 | ||
|
|
1e3e71c93b | ||
|
|
16d85fd93d | ||
|
|
085829ea08 | ||
|
|
015488b4e4 | ||
|
|
46ade526c9 | ||
|
|
f11eee1f3c | ||
|
|
6241a66d2d | ||
|
|
6cba45c140 | ||
|
|
dfcdf04d1c | ||
|
|
a8864d36ad | ||
|
|
6e058b4f93 | ||
|
|
7d1b6b7ee0 | ||
|
|
78b8ba345c | ||
|
|
5dcee37fa6 | ||
|
|
bae5c77574 | ||
|
|
51a45a6f56 | ||
|
|
32a480e21d | ||
|
|
c7c731ba5f | ||
|
|
bbf09490de | ||
|
|
e9d2b6af63 | ||
|
|
c0f3658e2f | ||
|
|
7a2a21cc20 | ||
|
|
b31bbe8002 | ||
|
|
d53e7640fd | ||
|
|
26a57c0874 | ||
|
|
d28fe6c7ff | ||
|
|
57a5b70e77 | ||
|
|
3a335d2839 | ||
|
|
2b074ae6f7 | ||
|
|
f725f50f83 | ||
|
|
623a74d812 | ||
|
|
916bc74779 | ||
|
|
d35641f517 | ||
|
|
77b2e3ad24 | ||
|
|
a8e67039c2 | ||
|
|
7b8dc691e3 | ||
|
|
1efc26d98f | ||
|
|
bce3b085db | ||
|
|
8c23e503bc | ||
|
|
3f165f5f80 | ||
|
|
024d54780e | ||
|
|
0b420d23c1 | ||
|
|
a0d95338d6 | ||
|
|
fee0c14800 | ||
|
|
e743dd3cf5 | ||
|
|
1a1af661aa | ||
|
|
2e0c8383c5 | ||
|
|
6a3699d2ea | ||
|
|
2c9942d069 | ||
|
|
ace62606b9 | ||
|
|
ab97ea91f0 | ||
|
|
d2b43d5ca9 | ||
|
|
210f90766d | ||
|
|
61d2386401 | ||
|
|
749f0a9d11 | ||
|
|
2e5cd4be96 | ||
|
|
a6848d5e4e | ||
|
|
984c4d81ad | ||
|
|
bb490815c2 | ||
|
|
aa2315612d | ||
|
|
e03be127aa | ||
|
|
3f1e46e723 | ||
|
|
f42bd111ee | ||
|
|
b98be37baf | ||
|
|
c7bb3485e5 | ||
|
|
3eb0e92961 | ||
|
|
01872b0981 | ||
|
|
7f741f9517 | ||
|
|
15e99b27d5 | ||
|
|
251537026e | ||
|
|
aa3621bd7f | ||
|
|
55c8f4402d | ||
|
|
838588a2ea | ||
|
|
0c93e20971 | ||
|
|
c220bffdbc | ||
|
|
72cccedc60 | ||
|
|
ddbadcf671 | ||
|
|
184a652a21 | ||
|
|
c13f9e4084 | ||
|
|
0acb5fc51c | ||
|
|
3a789caf53 | ||
|
|
471e1bfefe | ||
|
|
8caa1386c9 | ||
|
|
8dc36fd58e | ||
|
|
5ee3a5618b | ||
|
|
3e2cf54d88 | ||
|
|
2c1ff372ef | ||
|
|
35b53f1615 | ||
|
|
c525e048ae | ||
|
|
246c434bce | ||
|
|
c368d78516 | ||
|
|
6cef736aa5 | ||
|
|
509579cff0 | ||
|
|
5808646932 | ||
|
|
d0173e0b8c | ||
|
|
d22f8477bb | ||
|
|
05bd0a125a | ||
|
|
ebecc5af19 | ||
|
|
7ed8caa7df | ||
|
|
4ff7a02cf4 | ||
|
|
646771b573 | ||
|
|
b1d01b49da | ||
|
|
dbc059ac33 | ||
|
|
8fd00c61f9 | ||
|
|
22f8535000 | ||
|
|
5f1e4cb34d | ||
|
|
544cb3c9c7 | ||
|
|
199d05b32d | ||
|
|
e8ebde087d | ||
|
|
0d1a238dc3 | ||
|
|
fe98599f6e | ||
|
|
914ca80d4c | ||
|
|
38ecc6c5ed | ||
|
|
c042226428 | ||
|
|
40275ce377 | ||
|
|
7d01bf4a8f | ||
|
|
62800e0d14 | ||
|
|
51b3427289 | ||
|
|
aeb5a16333 | ||
|
|
f0871041e9 | ||
|
|
e67f8e1cca | ||
|
|
bef0e36ecb | ||
|
|
ef2fa5b3cc | ||
|
|
e3bee07ec4 | ||
|
|
1455be36a2 | ||
|
|
b57bf89f3c | ||
|
|
106a9daa1c | ||
|
|
3d7726e93a | ||
|
|
b1202af131 | ||
|
|
b1dc0ec4a5 | ||
|
|
bd5396dec3 | ||
|
|
7de9dced00 | ||
|
|
38990629e1 | ||
|
|
6ca814c3d9 | ||
|
|
841085c585 | ||
|
|
5dc70e48af | ||
|
|
fbb1141e46 | ||
|
|
7b757600a8 | ||
|
|
e4435e3121 | ||
|
|
576b698cd6 | ||
|
|
c03ed8027e | ||
|
|
6dbb4970dc | ||
|
|
d1383116b7 | ||
|
|
62528cbd46 | ||
|
|
a1fee26f98 | ||
|
|
7f6056ecc2 | ||
|
|
4706196f95 | ||
|
|
a4fde9154f | ||
|
|
5474536802 | ||
|
|
73c71e5c4b | ||
|
|
3e57ad3f75 | ||
|
|
efde9ea8f2 | ||
|
|
0cf7a53a2f | ||
|
|
80b33a8058 | ||
|
|
8430b28fda | ||
|
|
6292cabbb7 | ||
|
|
db804107ef | ||
|
|
214f541d02 | ||
|
|
2a04e43d51 | ||
|
|
1d93e439ce | ||
|
|
be4a62470e | ||
|
|
b2c3c365ff | ||
|
|
e605090dfe | ||
|
|
0addf44aa5 | ||
|
|
ed7c41589c | ||
|
|
bd2ee36aa6 | ||
|
|
a596520964 | ||
|
|
84cad99f20 | ||
|
|
303b721c8d | ||
|
|
ca74962010 | ||
|
|
ef6f687dce | ||
|
|
20d2d868bc | ||
|
|
51d4ba170c | ||
|
|
4b5b4b646b | ||
|
|
45e3c0e2db | ||
|
|
4224e564a5 | ||
|
|
b6a80a6bc6 | ||
|
|
565fe66958 | ||
|
|
00c91abbf0 | ||
|
|
fd3672b40c | ||
|
|
41e863f0bc | ||
|
|
29d9ab78be | ||
|
|
02ecd6e86b | ||
|
|
99cc479e09 | ||
|
|
bc6908b453 | ||
|
|
b75a2508da | ||
|
|
14655fa683 | ||
|
|
93b43a58db | ||
|
|
b7b8553c23 | ||
|
|
d274f16b12 | ||
|
|
e9305be96f | ||
|
|
551d706b7f | ||
|
|
6368bc35ef | ||
|
|
ef44f0e9f7 | ||
|
|
029b24969c | ||
|
|
3c5ead03e2 | ||
|
|
85a5f2042d | ||
|
|
35018cdad2 | ||
|
|
94adebd220 | ||
|
|
7b365afdcd | ||
|
|
9391fdc868 | ||
|
|
f8c3aa1172 | ||
|
|
9550541aaa | ||
|
|
bf0785148e | ||
|
|
cfd5a23b3d | ||
|
|
f6e4f0ab78 | ||
|
|
e30d5b02b4 | ||
|
|
7c62c9baf1 | ||
|
|
c037dfb533 | ||
|
|
15dc794093 | ||
|
|
0c6884481e | ||
|
|
0e0128b8b2 | ||
|
|
669108744d | ||
|
|
18cb5845a6 | ||
|
|
728a068a7b | ||
|
|
e6dd86719f | ||
|
|
8f932de25d | ||
|
|
93c9408419 | ||
|
|
a567936a7a | ||
|
|
ce3e7f7f81 | ||
|
|
6aace451df | ||
|
|
323859bed2 | ||
|
|
c1e199c188 | ||
|
|
817a027c55 | ||
|
|
73db5b0a9e | ||
|
|
73ab8213fd | ||
|
|
7789e20408 | ||
|
|
4aceb4244e | ||
|
|
843be6f760 | ||
|
|
3505e1ecf8 | ||
|
|
fc7986b654 | ||
|
|
258f61705a | ||
|
|
2e89f36b8f | ||
|
|
e288629ccb | ||
|
|
47a4acc6b7 | ||
|
|
39eb2a9c01 | ||
|
|
3582d6dd0a | ||
|
|
d053c1f79b | ||
|
|
bd375abe5c | ||
|
|
1c48b27046 | ||
|
|
3281f13fd1 | ||
|
|
af61146925 | ||
|
|
c423bee933 | ||
|
|
efe9bd68e7 | ||
|
|
fe15e06da7 | ||
|
|
aadaa2637e | ||
|
|
86fe039a99 | ||
|
|
5a414331fb | ||
|
|
0093dcb697 | ||
|
|
7d8e04f3d3 | ||
|
|
dbad949d28 | ||
|
|
d5b90d53f6 | ||
|
|
f5fae0ca73 | ||
|
|
8ce329afeb | ||
|
|
e958c342ea | ||
|
|
cb281783a5 | ||
|
|
5a3dd7eb57 | ||
|
|
b8c91011e5 | ||
|
|
acdc328cdc | ||
|
|
9a055c829b | ||
|
|
736fb18f34 | ||
|
|
6887c67642 | ||
|
|
bc571c1f42 | ||
|
|
bb6efbb43d | ||
|
|
1e495c483f | ||
|
|
62525a6075 | ||
|
|
508fbb3679 | ||
|
|
bac586faf7 | ||
|
|
8fe5a7a78e | ||
|
|
12d52c0798 | ||
|
|
d29f100bbe | ||
|
|
111908d84c | ||
|
|
6f79f3b232 | ||
|
|
853908d04f | ||
|
|
cb13ee6869 | ||
|
|
81e9d07802 | ||
|
|
66a40447a3 | ||
|
|
825b2f75ba | ||
|
|
81a6d84126 | ||
|
|
55d0c6905c | ||
|
|
50169b11dd | ||
|
|
718767919e | ||
|
|
23c295e82e | ||
|
|
5e4f8b67fb | ||
|
|
e77abf814f | ||
|
|
484ec75afd | ||
|
|
bddeb135a4 | ||
|
|
459a309dbe | ||
|
|
7bf77f2157 | ||
|
|
0a5a9f8a51 | ||
|
|
523d6dd5b4 | ||
|
|
3aca226373 | ||
|
|
2422f33469 | ||
|
|
06b60a25d0 | ||
|
|
5d51914842 | ||
|
|
61e8784dbb | ||
|
|
1d5265a0bd | ||
|
|
54c27ba534 | ||
|
|
8b24fa3326 | ||
|
|
8a3f0dd809 | ||
|
|
6821d1f379 | ||
|
|
0c823faf5b | ||
|
|
7caadb8d54 | ||
|
|
a2066c73b1 | ||
|
|
42198912a4 | ||
|
|
51978b7bf9 | ||
|
|
7011c50040 | ||
|
|
bffd099935 | ||
|
|
f109e44271 | ||
|
|
89ea48ffd9 | ||
|
|
bcd764308d | ||
|
|
88b232fd90 | ||
|
|
610a8b7e6c | ||
|
|
e8a75a102a | ||
|
|
4fd229cb47 | ||
|
|
81798596c8 | ||
|
|
489c98ed01 | ||
|
|
8ad8df921a | ||
|
|
a04723bdfe | ||
|
|
daf407be5c | ||
|
|
886314f153 | ||
|
|
06c0a42186 | ||
|
|
383a69ad31 | ||
|
|
2aa6c4eb37 | ||
|
|
98f8b6b826 | ||
|
|
e2fb1a0479 | ||
|
|
8d777e9046 | ||
|
|
c81062992a | ||
|
|
1956a3066a | ||
|
|
98620999f6 | ||
|
|
6b1d2f69b2 | ||
|
|
c5ab58c49f | ||
|
|
d45dd6ab19 | ||
|
|
511f6931b3 | ||
|
|
60e43fdf45 | ||
|
|
870a28b64f | ||
|
|
aa6db44911 | ||
|
|
26905f0742 | ||
|
|
ff41a035d1 | ||
|
|
28574c4737 | ||
|
|
2895da5ce8 | ||
|
|
d179e598c5 | ||
|
|
d948f94642 | ||
|
|
4d62af638d | ||
|
|
d4c41cd362 | ||
|
|
b7b376123e | ||
|
|
62c096ee3c | ||
|
|
fd4837ed4b | ||
|
|
fcd1467d93 | ||
|
|
e861c81a60 | ||
|
|
41815e9bf2 | ||
|
|
9a24a4b934 | ||
|
|
075cbf8516 | ||
|
|
191e8c6ef0 | ||
|
|
4fc83c7acc | ||
|
|
e31127d595 | ||
|
|
a71c50e836 | ||
|
|
7d25c311a0 | ||
|
|
c4ca844851 | ||
|
|
4678e389d3 | ||
|
|
468036440e | ||
|
|
10be67bd24 | ||
|
|
62ed89eb54 | ||
|
|
0de2d07f5f | ||
|
|
ea3ec63943 | ||
|
|
0a19b06a9d | ||
|
|
5e55df428d | ||
|
|
6c4ce7361d | ||
|
|
01ad482f61 | ||
|
|
24cfabe841 | ||
|
|
1a9dfd4b16 | ||
|
|
ff36d827a7 | ||
|
|
1310043b67 | ||
|
|
01488c7605 | ||
|
|
99baba9cba | ||
|
|
7f4aebdb73 | ||
|
|
4696c38514 | ||
|
|
ce1a9be259 | ||
|
|
5397d7a085 | ||
|
|
a3814cb6a1 | ||
|
|
f571558813 | ||
|
|
4e29b00eac | ||
|
|
9487bbd680 | ||
|
|
a83749f160 | ||
|
|
e3fa431b06 | ||
|
|
d646ad0921 | ||
|
|
a629ec0067 | ||
|
|
067f568d1c | ||
|
|
5dd7fd23be | ||
|
|
961433eebe | ||
|
|
f83c50ef40 | ||
|
|
85ff28c2db | ||
|
|
47a7aeb224 | ||
|
|
f243e80997 | ||
|
|
84c7617063 | ||
|
|
982fed28e0 | ||
|
|
52d9292837 | ||
|
|
0db2fabef4 | ||
|
|
52531b46f0 | ||
|
|
7408e67789 | ||
|
|
9612d86545 | ||
|
|
fed6475003 | ||
|
|
aed463219a | ||
|
|
e6574ede9d | ||
|
|
c89bc7c42a | ||
|
|
29ba041279 | ||
|
|
ebde0efa54 | ||
|
|
c58f6249f1 | ||
|
|
40f98870d8 | ||
|
|
04f414606e | ||
|
|
250a86b07e | ||
|
|
490a592445 | ||
|
|
225b2f5412 | ||
|
|
5b009fc416 | ||
|
|
6211b43ea5 | ||
|
|
91e2da9f48 | ||
|
|
4a699fea63 | ||
|
|
3b104c72ab | ||
|
|
157341b14a | ||
|
|
327c90c55a | ||
|
|
4e364fa704 | ||
|
|
079178a02b | ||
|
|
1ceb6e4aad | ||
|
|
1e955fcaf5 | ||
|
|
c353b25664 | ||
|
|
8a9d832813 | ||
|
|
882aa159ef | ||
|
|
35d0a5102c | ||
|
|
28b77ddbb5 | ||
|
|
ee2afe042a | ||
|
|
80eb19a118 | ||
|
|
e17ed72439 | ||
|
|
670b84bf2a | ||
|
|
6af8795ca2 | ||
|
|
2423844b8d | ||
|
|
b0a464ab0a | ||
|
|
a221864770 | ||
|
|
4ed008976e | ||
|
|
8c863c92a3 | ||
|
|
af68af14e7 | ||
|
|
2c136bc222 | ||
|
|
d7928e36d4 |
@@ -0,0 +1 @@
|
||||
local.properties
|
||||
@@ -0,0 +1,19 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{kt,kts}]
|
||||
ij_kotlin_imports_layout = *
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_discouraged-comment-location = disabled
|
||||
ktlint_standard_function-expression-body = disabled
|
||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report
|
||||
---
|
||||
|
||||
## 🐛 Describe the bug
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## ⚠️ Current behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## ✅ Expected behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## 💣 Steps to reproduce
|
||||
|
||||
<!-- How we can reproduce the behavior: -->
|
||||
|
||||
## 📷 Screenshots
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
## 📱 Tech info
|
||||
|
||||
- Device: <!-- e.g. Nexus One -->
|
||||
- OS: <!-- e.g. 7.1.1 -->
|
||||
- App version: <!-- e.g. 1.0.0 -->
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
## ⚠️ Is your feature request related to a problem? Please describe
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
## 💡 Describe the solution you'd like
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## 🤚 Do you want to develop this feature yourself?
|
||||
|
||||
<!-- Put an `x` symbol into braces of desired choice. -->
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
@@ -0,0 +1,27 @@
|
||||
## 🚀 Description
|
||||
<!-- Describe your changes in detail -->
|
||||
|
||||
## 📄 Motivation and Context
|
||||
<!-- Why is this change required? What problem does it solve? -->
|
||||
<!-- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## 🧪 How Has This Been Tested?
|
||||
<!-- Please describe in detail how you tested your changes. -->
|
||||
<!-- Include details of your testing environment, tests ran to see how -->
|
||||
<!-- your change affects other areas of the code, etc. -->
|
||||
|
||||
## 📷 Screenshots (if appropriate)
|
||||
<!-- Please provide a screenshot of your change -->
|
||||
|
||||
## 📦 Types of changes
|
||||
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
|
||||
## ✅ Checklist
|
||||
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
@@ -10,108 +10,161 @@ jobs:
|
||||
unit_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
run: ./gradlew allTests
|
||||
|
||||
- name: Generate code coverage
|
||||
run: ./gradlew shared:koverXmlReportDebug
|
||||
run: ./gradlew shared:koverXmlReport
|
||||
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
build:
|
||||
needs: unit_tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
format: [
|
||||
{ type: "apk", command: "assembleRelease", output: "apk" },
|
||||
{ type: "aab", command: "generateReleaseBaseline bundleRelease", output: "bundle" }
|
||||
{
|
||||
type: "apk",
|
||||
command: "androidApp:assembleRelease
|
||||
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
|
||||
-Pandroid.testoptions.manageddevices.emulator.gpu=\"swiftshader_indirect\"
|
||||
-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
|
||||
-Pandroid.experimental.androidTest.numManagedDeviceShards=1
|
||||
-Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1
|
||||
--no-configuration-cache",
|
||||
output: "apk"
|
||||
},
|
||||
{
|
||||
type: "aab",
|
||||
command: "androidApp:generateReleaseBaseline
|
||||
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
|
||||
-Pandroid.testoptions.manageddevices.emulator.gpu=\"swiftshader_indirect\"
|
||||
-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
|
||||
-Pandroid.experimental.androidTest.numManagedDeviceShards=1
|
||||
-Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1
|
||||
--no-configuration-cache
|
||||
androidApp:bundleRelease",
|
||||
output: "bundle"
|
||||
}
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: Clean GMD
|
||||
run: ./gradlew cleanManagedDevices --unused-only
|
||||
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: ${{ secrets.KEYSTORE_ENCODED }}
|
||||
run: |
|
||||
echo $ENCODED_KEYSTORE > keystore-b64.txt
|
||||
base64 -d keystore-b64.txt > ./androidApp/keystore.jks
|
||||
|
||||
- name: Build release
|
||||
env:
|
||||
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
|
||||
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASS }}
|
||||
KEYSTORE_PATH: keystore.jks
|
||||
run: ./gradlew ${{ matrix.format.command }}
|
||||
|
||||
- name: Upload Build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: app-release.${{ matrix.format.type }}
|
||||
path: androidApp/build/outputs/${{ matrix.format.output }}/release/*.${{ matrix.format.type }}
|
||||
|
||||
- name: Upload mapping folder
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: mapping-${{ matrix.format.type }}
|
||||
path: androidApp/build/outputs/mapping/
|
||||
|
||||
release:
|
||||
needs: build
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Tag Release
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
git push origin ${{ github.event.inputs.version }}
|
||||
git tag android-${{ github.event.inputs.version }}
|
||||
git push origin android-${{ github.event.inputs.version }}
|
||||
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: app-release.apk
|
||||
|
||||
- name: Download APK mapping
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mapping-apk
|
||||
path: mapping-apk
|
||||
|
||||
- name: Zip APK mapping
|
||||
run: |
|
||||
zip -r mapping-apk.zip mapping-apk/
|
||||
|
||||
- name: Download AAB
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: app-release.aab
|
||||
|
||||
- name: Download AAB mapping
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mapping-aab
|
||||
path: mapping-aab
|
||||
|
||||
- name: Zip AAB mapping
|
||||
run: |
|
||||
zip -r mapping-aab.zip mapping-aab/
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: "app-release.apk,app-release.aab,mapping-apk.zip,mapping-aab.zip"
|
||||
artifacts: "*.apk,*.aab,mapping-apk.zip,mapping-aab.zip"
|
||||
draft: true
|
||||
name: ${{ github.event.inputs.version }}
|
||||
tag: ${{ github.event.inputs.version }}
|
||||
name: "[Android] ${{ github.event.inputs.version }}"
|
||||
tag: android-${{ github.event.inputs.version }}
|
||||
|
||||
@@ -10,36 +10,37 @@ jobs:
|
||||
code_quality:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Unit tests
|
||||
run: ./gradlew test
|
||||
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
build_msi:
|
||||
needs: code_quality
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Build MSI
|
||||
run: |
|
||||
./gradlew :desktopApp:packageReleaseMsi
|
||||
- name: Upload MSI
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: CPU.Info-${{ github.event.inputs.version }}.msi
|
||||
path: desktopApp/build/compose/binaries/main-release/msi/*.msi
|
||||
@@ -48,51 +49,72 @@ jobs:
|
||||
needs: code_quality
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Build Uber JAR
|
||||
run: ./gradlew :desktopApp:packageReleaseUberJarForCurrentOS
|
||||
- name: Upload Uber JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: CPU-Info-linux-x64-${{ github.event.inputs.version }}-release.jar
|
||||
path: desktopApp/build/compose/jars/*.jar
|
||||
|
||||
build_mac_dmg:
|
||||
build_mac_uber_jar:
|
||||
needs: code_quality
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Build Uber JAR
|
||||
run: ./gradlew :desktopApp:packageReleaseUberJarForCurrentOS
|
||||
- name: Upload Uber JAR
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: CPU-Info-macos-arm64-${{ github.event.inputs.version }}-release.jar
|
||||
path: desktopApp/build/compose/jars/*.jar
|
||||
|
||||
build_mac_arm64_dmg:
|
||||
needs: code_quality
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
key: ${{ runner.os }}-v1-${{ hashFiles('*.versions.toml') }}
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MAC_NOTARIZATION_CERT }}
|
||||
p12-password: ${{ secrets.MAC_NOTARIZATION_CERT_PASS }}
|
||||
@@ -100,8 +122,8 @@ jobs:
|
||||
- name: Create path variables
|
||||
id: path_variables
|
||||
run: |
|
||||
name="CPU-Info-${{ github.event.inputs.version }}.dmg"
|
||||
path="desktopApp/build/compose/binaries/main-release/dmg/${name}"
|
||||
name="CPU-Info-arm64-${{ github.event.inputs.version }}.dmg"
|
||||
path="desktopApp/build/compose/binaries/main-release/dmg/CPU-Info-${{ github.event.inputs.version }}.dmg"
|
||||
echo "RELEASE_NAME=$name" >> $GITHUB_ENV
|
||||
echo "RELEASE_PATH=$path" >> $GITHUB_ENV
|
||||
|
||||
@@ -118,28 +140,98 @@ jobs:
|
||||
RELEASE_PATH: ${{ env.RELEASE_PATH }}
|
||||
|
||||
- name: Upload DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
path: ${{ env.RELEASE_PATH }}
|
||||
|
||||
build_mac_x64_dmg:
|
||||
needs: code_quality
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
key: ${{ runner.os }}-v1-${{ hashFiles('*.versions.toml') }}
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MAC_NOTARIZATION_CERT }}
|
||||
p12-password: ${{ secrets.MAC_NOTARIZATION_CERT_PASS }}
|
||||
|
||||
- name: Create path variables
|
||||
id: path_variables
|
||||
run: |
|
||||
name="CPU-Info-x64-${{ github.event.inputs.version }}.dmg"
|
||||
path="desktopApp/build/compose/binaries/main-release/dmg/CPU-Info-${{ github.event.inputs.version }}.dmg"
|
||||
echo "RELEASE_NAME=$name" >> $GITHUB_ENV
|
||||
echo "RELEASE_PATH=$path" >> $GITHUB_ENV
|
||||
|
||||
- name: Create and notarize DMG
|
||||
run: |
|
||||
./gradlew desktopApp:notarizeReleaseDmg -PmacOsNotarization=true \
|
||||
-Pcompose.desktop.mac.notarization.appleID=$MAC_NOTARIZATION_ID \
|
||||
-Pcompose.desktop.mac.notarization.password=$MAC_NOTARIZATION_PWD \
|
||||
-Pcompose.desktop.mac.notarization.teamID=$MAC_APPSTORE_TEAM_ID
|
||||
env:
|
||||
MAC_NOTARIZATION_ID: ${{ secrets.MAC_NOTARIZATION_ID }}
|
||||
MAC_NOTARIZATION_PWD: ${{ secrets.MAC_NOTARIZATION_PWD }}
|
||||
MAC_APPSTORE_TEAM_ID: ${{ secrets.MAC_APPSTORE_TEAM_ID }}
|
||||
RELEASE_PATH: ${{ env.RELEASE_PATH }}
|
||||
|
||||
- name: Upload DMG
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
path: ${{ env.RELEASE_PATH }}
|
||||
|
||||
tag_and_create_release:
|
||||
needs: [ build_msi, build_linux_x64_uber_jar, build_mac_dmg ]
|
||||
needs: [ build_msi, build_linux_x64_uber_jar, build_mac_arm64_dmg, build_mac_x64_dmg, build_mac_uber_jar ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download MSI
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CPU.Info-${{ github.event.inputs.version }}.msi
|
||||
- name: Download Linux x64 Uber JAR
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CPU-Info-linux-x64-${{ github.event.inputs.version }}-release.jar
|
||||
- name: Download DMG
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Download Mac x64 DMG
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CPU-Info-${{ github.event.inputs.version }}.dmg
|
||||
name: CPU-Info-x64-${{ github.event.inputs.version }}.dmg
|
||||
- name: Rename Mac x64 DMG file
|
||||
run: mv CPU-Info-${{ github.event.inputs.version }}.dmg CPU-Info-macos-x64-${{ github.event.inputs.version }}.dmg
|
||||
- name: Download Mac arm64 DMG
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CPU-Info-arm64-${{ github.event.inputs.version }}.dmg
|
||||
- name: Rename Mac arm64 DMG file
|
||||
run: mv CPU-Info-${{ github.event.inputs.version }}.dmg CPU-Info-macos-arm64-${{ github.event.inputs.version }}.dmg
|
||||
- name: Download Mac OS Uber JAR
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: CPU-Info-macos-arm64-${{ github.event.inputs.version }}-release.jar
|
||||
- name: Tag Release
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
|
||||
@@ -8,37 +8,39 @@ on:
|
||||
|
||||
jobs:
|
||||
code_quality:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
build_and_release:
|
||||
needs: code_quality
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
@@ -47,12 +49,12 @@ jobs:
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.IOS_CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.IOS_CERTIFICATES_PASSWORD }}
|
||||
- name: Download provisioning profiles
|
||||
uses: apple-actions/download-provisioning-profiles@v3
|
||||
uses: apple-actions/download-provisioning-profiles@v5
|
||||
with:
|
||||
bundle-id: ${{ secrets.IOS_BUNDLE_ID }}
|
||||
issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
|
||||
@@ -116,9 +118,9 @@ jobs:
|
||||
-archivePath "${RUNNER_TEMP}/Build/Archives/cpuinfo.xcarchive" \
|
||||
-exportPath "${RUNNER_TEMP}/Build/Archives/cpuinfo.xcarchive" \
|
||||
PRODUCT_BUNDLE_IDENTIFIER="${{ secrets.IOS_BUNDLE_ID }}"
|
||||
|
||||
|
||||
echo "ipa_path=${RUNNER_TEMP}/Build/Archives/cpuinfo.xcarchive/CPU Info.ipa" >> $GITHUB_ENV
|
||||
- uses: Apple-Actions/upload-testflight-build@v1
|
||||
- uses: Apple-Actions/upload-testflight-build@v4
|
||||
with:
|
||||
app-path: ${{ env.ipa_path }}
|
||||
issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
|
||||
@@ -129,4 +131,4 @@ jobs:
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag ios-${{ github.event.inputs.version }}
|
||||
git push origin ios-${{ github.event.inputs.version }}
|
||||
git push origin ios-${{ github.event.inputs.version }}
|
||||
|
||||
@@ -8,40 +8,42 @@ on:
|
||||
|
||||
jobs:
|
||||
code_quality:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
build_and_release:
|
||||
needs: code_quality
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
@@ -52,7 +54,7 @@ jobs:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MAC_CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.MAC_CERTIFICATES_PASSWORD }}
|
||||
@@ -74,10 +76,10 @@ jobs:
|
||||
- name: Create PKG
|
||||
run: ./gradlew packageReleasePkg -PmacOsAppStoreRelease=true
|
||||
|
||||
- uses: Apple-Actions/upload-testflight-build@v1
|
||||
- uses: Apple-Actions/upload-testflight-build@v4
|
||||
with:
|
||||
app-type: 'osx'
|
||||
app-path: desktopApp/build/compose/binaries/main-release/pkg/CPU-Info-${{ github.event.inputs.version }}.pkg
|
||||
issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
|
||||
api-key-id: ${{ secrets.IOS_APPSTORE_KEY_ID }}
|
||||
api-private-key: ${{ secrets.IOS_APPSTORE_PRIVATE_KEY }}
|
||||
api-private-key: ${{ secrets.IOS_APPSTORE_PRIVATE_KEY }}
|
||||
|
||||
@@ -6,23 +6,25 @@ on:
|
||||
- master
|
||||
jobs:
|
||||
unit_tests:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
run: ./gradlew allTests
|
||||
- name: Generate code coverage
|
||||
run: ./gradlew shared:koverXmlReportDebug
|
||||
run: ./gradlew shared:koverXmlReport
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
name: Wear OS release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
unit_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: corretto
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Unit tests
|
||||
run: ./gradlew allTests
|
||||
- name: Generate code coverage
|
||||
run: './gradlew shared:koverXmlReport'
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
- name: Static checks
|
||||
run: ./gradlew spotlessCheck
|
||||
build:
|
||||
needs: unit_tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
format:
|
||||
- type: apk
|
||||
command: 'wearOsApp:assembleRelease'
|
||||
output: apk
|
||||
- type: aab
|
||||
command: 'wearOsApp:bundleRelease'
|
||||
output: bundle
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: corretto
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: '${{ secrets.KEYSTORE_ENCODED }}'
|
||||
run: |
|
||||
echo $ENCODED_KEYSTORE > keystore-b64.txt
|
||||
base64 -d keystore-b64.txt > ./wearOsApp/keystore.jks
|
||||
- name: Build release
|
||||
env:
|
||||
KEYSTORE_ALIAS: '${{ secrets.KEYSTORE_ALIAS }}'
|
||||
KEYSTORE_PASS: '${{ secrets.KEYSTORE_PASS }}'
|
||||
KEYSTORE_PATH: keystore.jks
|
||||
run: './gradlew ${{ matrix.format.command }}'
|
||||
- name: Upload Build
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: 'app-release.${{ matrix.format.type }}'
|
||||
path: >-
|
||||
wearOsApp/build/outputs/${{ matrix.format.output }}/release/*.${{
|
||||
matrix.format.type }}
|
||||
- name: Upload mapping folder
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: 'mapping-${{ matrix.format.type }}'
|
||||
path: wearOsApp/build/outputs/mapping/
|
||||
release:
|
||||
needs: build
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Tag Release
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag wear-${{ github.event.inputs.version }}
|
||||
git push origin wear-${{ github.event.inputs.version }}
|
||||
- name: Download APK
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: app-release.apk
|
||||
- name: Download APK mapping
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mapping-apk
|
||||
path: mapping-apk
|
||||
- name: Zip APK mapping
|
||||
run: |
|
||||
zip -r mapping-apk.zip mapping-apk/
|
||||
- name: Download AAB
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: app-release.aab
|
||||
- name: Download AAB mapping
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: mapping-aab
|
||||
path: mapping-aab
|
||||
- name: Zip AAB mapping
|
||||
run: |
|
||||
zip -r mapping-aab.zip mapping-aab/
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: '*.apk,*.aab,mapping-apk.zip,mapping-aab.zip'
|
||||
draft: true
|
||||
name: '[WearOS] ${{ github.event.inputs.version }}'
|
||||
tag: 'wear-${{ github.event.inputs.version }}'
|
||||
@@ -5,6 +5,7 @@ local.properties
|
||||
build/
|
||||
captures/
|
||||
.cxx/
|
||||
.kotlin/
|
||||
|
||||
.idea/
|
||||
!/.idea/codeStyles
|
||||
@@ -18,4 +19,4 @@ captures
|
||||
!*.xcodeproj/xcshareddata/
|
||||
!*.xcodeproj/project.xcworkspace/
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="android" withSubpackages="true" static="true" />
|
||||
<package name="androidx" withSubpackages="true" static="true" />
|
||||
<package name="com" withSubpackages="true" static="true" />
|
||||
<package name="junit" withSubpackages="true" static="true" />
|
||||
<package name="net" withSubpackages="true" static="true" />
|
||||
<package name="org" withSubpackages="true" static="true" />
|
||||
<package name="java" withSubpackages="true" static="true" />
|
||||
<package name="javax" withSubpackages="true" static="true" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="androidx" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||

|
||||
|
||||
<img src="info/icon_glow.png" width="100" height="100" />
|
||||
<img src="info/icon_glow.png" width="100" height="100" alt="CPU Info Icon" />
|
||||
|
||||
# CPU Info
|
||||
|
||||
CPU Info provides information about device hardware and software.
|
||||
CPU Info provides comprehensive information about your device's hardware and software.
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
@@ -28,14 +28,17 @@ height="80">](https://flathub.org/apps/com.kgurgul.cpuinfo)
|
||||
[<img src="info/ms_badge.png"
|
||||
alt="Get it from Microsoft"
|
||||
height="80">](https://apps.microsoft.com/detail/9phxq0f4knbl)
|
||||
[<img src="info/get-it-on-homebrew.png"
|
||||
alt="Get it from Homebrew"
|
||||
height="80">](https://formulae.brew.sh/cask/cpu-info)
|
||||
|
||||
# Supported platforms
|
||||
## Supported platforms
|
||||
|
||||
* Android
|
||||
* iOS
|
||||
* Desktop (JVM)
|
||||
| Android | Android TV | Wear OS | iOS | Desktop | Wasm |
|
||||
|:-------:|:----------:|:-------:|:---:|:-------:|:-------------------------------------------------------:|
|
||||
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅<br/>[In preview](https://kgurgul.com/assets/cpuinfo/) |
|
||||
|
||||
# Tech stack
|
||||
## Tech stack
|
||||
|
||||
* [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html)
|
||||
* [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/)
|
||||
@@ -44,17 +47,10 @@ height="80">](https://apps.microsoft.com/detail/9phxq0f4knbl)
|
||||
* [Koin](https://github.com/InsertKoinIO/koin)
|
||||
* [Coil](https://github.com/coil-kt/coil)
|
||||
* [DataStore](https://developer.android.com/kotlin/multiplatform/datastore)
|
||||
* External C libraries - [pytorch/cpuinfo](https://github.com/pytorch/cpuinfo)
|
||||
* [pytorch/cpuinfo](https://github.com/pytorch/cpuinfo)
|
||||
* [OSHI](https://github.com/oshi/oshi)
|
||||
|
||||
# TODO
|
||||
|
||||
* Shared unit and UI tests
|
||||
* Add more information about battery
|
||||
* Bring back apps size - use StorageStatsManager on Android if possible
|
||||
* Add translations framework
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
Copyright 2017 KG Soft
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
/build
|
||||
src/debug/google-services.json
|
||||
src/release/google-services.json
|
||||
@@ -1,38 +1,39 @@
|
||||
import com.kgurgul.AndroidVersions
|
||||
|
||||
plugins {
|
||||
kotlin("android")
|
||||
alias(libs.plugins.androidTest)
|
||||
alias(libs.plugins.baselineprofile)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.kgurgul.cpuinfo.baselineprofile"
|
||||
compileSdk = Versions.COMPILE_SDK
|
||||
compileSdk = AndroidVersions.COMPILE_SDK
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
minSdk = 28
|
||||
targetSdk = AndroidVersions.TARGET_SDK
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
targetProjectPath = ":androidApp"
|
||||
|
||||
testOptions.managedDevices.devices {
|
||||
create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel6Api31") {
|
||||
testOptions.managedDevices.allDevices {
|
||||
create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel6Api33") {
|
||||
device = "Pixel 6"
|
||||
apiLevel = 31
|
||||
apiLevel = 33
|
||||
systemImageSource = "aosp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
managedDevices += "pixel6Api31"
|
||||
managedDevices += "pixel6Api33"
|
||||
useConnectedDevices = false
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ androidComponents {
|
||||
onVariants { v ->
|
||||
v.instrumentationRunnerArguments.put(
|
||||
"targetAppId",
|
||||
v.testedApks.map { v.artifacts.getBuiltArtifactsLoader().load(it)?.applicationId!! }
|
||||
v.testedApks.map { v.artifacts.getBuiltArtifactsLoader().load(it)?.applicationId!! },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
@@ -8,47 +23,21 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* This test class generates a basic startup baseline profile for the target package.
|
||||
*
|
||||
* We recommend you start with this but add important user flows to the profile to improve their performance.
|
||||
* Refer to the [baseline profile documentation](https://d.android.com/topic/performance/baselineprofiles)
|
||||
* for more information.
|
||||
*
|
||||
* You can run the generator with the "Generate Baseline Profile" run configuration in Android Studio or
|
||||
* the equivalent `generateBaselineProfile` gradle task:
|
||||
* ```
|
||||
* ./gradlew :app:generateReleaseBaselineProfile
|
||||
* ```
|
||||
* The run configuration runs the Gradle task and applies filtering to run only the generators.
|
||||
*
|
||||
* Check [documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args)
|
||||
* for more information about available instrumentation arguments.
|
||||
*
|
||||
* After you run the generator, you can verify the improvements running the [StartupBenchmarks] benchmark.
|
||||
*
|
||||
* When using this class to generate a baseline profile, only API 33+ or rooted API 28+ are supported.
|
||||
*
|
||||
* The minimum required version of androidx.benchmark to generate a baseline profile is 1.2.0.
|
||||
**/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class BaselineProfileGenerator {
|
||||
|
||||
@get:Rule
|
||||
val rule = BaselineProfileRule()
|
||||
@get:Rule val rule = BaselineProfileRule()
|
||||
|
||||
@Test
|
||||
fun generate() {
|
||||
val packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
|
||||
?: throw Exception("targetAppId not passed as instrumentation runner arg")
|
||||
rule.collect(
|
||||
packageName = packageName,
|
||||
includeInStartupProfile = true,
|
||||
) {
|
||||
val packageName =
|
||||
InstrumentationRegistry.getArguments().getString("targetAppId")
|
||||
?: throw Exception("targetAppId not passed as instrumentation runner arg")
|
||||
rule.collect(packageName = packageName, includeInStartupProfile = true) {
|
||||
pressHome()
|
||||
startActivityAndWait()
|
||||
waitForAsyncContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
@@ -10,4 +25,4 @@ fun MacrobenchmarkScope.waitForAsyncContent() {
|
||||
device.wait(Until.hasObject(By.res("cpu_info_lazy_column")), 5_000)
|
||||
val element = UiScrollable(UiSelector().scrollable(true))
|
||||
element.scrollToEnd(3)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.BaselineProfileMode
|
||||
@@ -12,56 +27,33 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* This test class benchmarks the speed of app startup.
|
||||
* Run this benchmark to verify how effective a Baseline Profile is.
|
||||
* It does this by comparing [CompilationMode.None], which represents the app with no Baseline
|
||||
* Profiles optimizations, and [CompilationMode.Partial], which uses Baseline Profiles.
|
||||
*
|
||||
* Run this benchmark to see startup measurements and captured system traces for verifying
|
||||
* the effectiveness of your Baseline Profiles. You can run it directly from Android
|
||||
* Studio as an instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease,
|
||||
* with this Gradle task:
|
||||
* ```
|
||||
* ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest
|
||||
* ```
|
||||
*
|
||||
* You should run the benchmarks on a physical device, not an Android emulator, because the
|
||||
* emulator doesn't represent real world performance and shares system resources with its host.
|
||||
*
|
||||
* For more information, see the [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark)
|
||||
* and the [instrumentation arguments documentation](https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
|
||||
**/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class StartupBenchmarks {
|
||||
|
||||
@get:Rule
|
||||
val rule = MacrobenchmarkRule()
|
||||
@get:Rule val rule = MacrobenchmarkRule()
|
||||
|
||||
@Test
|
||||
fun startupCompilationNone() = benchmark(CompilationMode.None())
|
||||
@Test fun startupCompilationNone() = benchmark(CompilationMode.None())
|
||||
|
||||
@Test
|
||||
fun startupCompilationBaselineProfiles() =
|
||||
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
|
||||
|
||||
private fun benchmark(compilationMode: CompilationMode) {
|
||||
val packageName = InstrumentationRegistry.getArguments().getString("targetAppId")
|
||||
?: throw Exception("targetAppId not passed as instrumentation runner arg")
|
||||
val packageName =
|
||||
InstrumentationRegistry.getArguments().getString("targetAppId")
|
||||
?: throw Exception("targetAppId not passed as instrumentation runner arg")
|
||||
rule.measureRepeated(
|
||||
packageName = packageName,
|
||||
metrics = listOf(StartupTimingMetric()),
|
||||
compilationMode = compilationMode,
|
||||
startupMode = StartupMode.COLD,
|
||||
iterations = 10,
|
||||
setupBlock = {
|
||||
pressHome()
|
||||
},
|
||||
setupBlock = { pressHome() },
|
||||
measureBlock = {
|
||||
startActivityAndWait()
|
||||
waitForAsyncContent()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import com.github.jk1.license.filter.DependencyFilter
|
||||
import com.github.jk1.license.filter.LicenseBundleNormalizer
|
||||
import com.github.jk1.license.render.JsonReportRenderer
|
||||
import com.github.jk1.license.render.ReportRenderer
|
||||
import com.kgurgul.AndroidSigningConfig
|
||||
import com.kgurgul.AndroidVersions
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.baselineprofile)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.licenses)
|
||||
alias(libs.plugins.serialization)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidTarget()
|
||||
sourceSets {
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":shared"))
|
||||
}
|
||||
}
|
||||
}
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = Versions.COMPILE_SDK
|
||||
compileSdk = AndroidVersions.COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.kgurgul.cpuinfo"
|
||||
|
||||
minSdk = Versions.MIN_SDK
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
versionCode = Versions.VERSION_CODE
|
||||
versionName = Versions.VERSION_NAME
|
||||
minSdk = AndroidVersions.MIN_SDK
|
||||
targetSdk = AndroidVersions.TARGET_SDK
|
||||
versionCode = AndroidVersions.VERSION_CODE
|
||||
versionName = AndroidVersions.VERSION_NAME
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -37,18 +37,19 @@ android {
|
||||
|
||||
signingConfigs {
|
||||
getByName("debug") {
|
||||
val debugSigningConfig = SigningConfig.getDebugProperties(rootProject.rootDir)
|
||||
storeFile = file(debugSigningConfig.getProperty(SigningConfig.KEY_PATH))
|
||||
keyAlias = debugSigningConfig.getProperty(SigningConfig.KEY_ALIAS)
|
||||
keyPassword = debugSigningConfig.getProperty(SigningConfig.KEY_PASS)
|
||||
storePassword = debugSigningConfig.getProperty(SigningConfig.KEY_PASS)
|
||||
val debugSigningConfig = AndroidSigningConfig.getDebugProperties(rootProject.rootDir)
|
||||
storeFile = file(debugSigningConfig.getProperty(AndroidSigningConfig.KEY_PATH))
|
||||
keyAlias = debugSigningConfig.getProperty(AndroidSigningConfig.KEY_ALIAS)
|
||||
keyPassword = debugSigningConfig.getProperty(AndroidSigningConfig.KEY_PASS)
|
||||
storePassword = debugSigningConfig.getProperty(AndroidSigningConfig.KEY_PASS)
|
||||
}
|
||||
create("release") {
|
||||
val releaseSigningConfig = SigningConfig.getReleaseProperties(rootProject.rootDir)
|
||||
storeFile = file(releaseSigningConfig.getProperty(SigningConfig.KEY_PATH))
|
||||
keyAlias = releaseSigningConfig.getProperty(SigningConfig.KEY_ALIAS)
|
||||
keyPassword = releaseSigningConfig.getProperty(SigningConfig.KEY_PASS)
|
||||
storePassword = releaseSigningConfig.getProperty(SigningConfig.KEY_PASS)
|
||||
val releaseSigningConfig =
|
||||
AndroidSigningConfig.getReleaseProperties(rootProject.rootDir)
|
||||
storeFile = file(releaseSigningConfig.getProperty(AndroidSigningConfig.KEY_PATH))
|
||||
keyAlias = releaseSigningConfig.getProperty(AndroidSigningConfig.KEY_ALIAS)
|
||||
keyPassword = releaseSigningConfig.getProperty(AndroidSigningConfig.KEY_PASS)
|
||||
storePassword = releaseSigningConfig.getProperty(AndroidSigningConfig.KEY_PASS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +57,10 @@ android {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
@@ -71,6 +75,12 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = false
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isReturnDefaultValues = true
|
||||
@@ -85,17 +95,8 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variantName = name
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
java.srcDir(File("build/generated/ksp/$variantName/kotlin"))
|
||||
}
|
||||
}
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,17 +109,15 @@ composeCompiler {
|
||||
metricsDestination = layout.buildDirectory.dir("compose_compiler")
|
||||
}
|
||||
|
||||
licenseReport {
|
||||
renderers = arrayOf<ReportRenderer>(JsonReportRenderer("licenses.json"))
|
||||
filters = arrayOf<DependencyFilter>(LicenseBundleNormalizer())
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(projects.shared)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.koin.annotations)
|
||||
ksp(libs.koin.kspCompiler)
|
||||
|
||||
implementation(libs.relinker)
|
||||
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
androidTestImplementation(libs.androidx.test.rules)
|
||||
androidTestImplementation(libs.androidx.test.ext)
|
||||
@@ -130,5 +129,5 @@ dependencies {
|
||||
|
||||
androidTestUtil(libs.androidx.test.orchestrator)
|
||||
|
||||
"baselineProfile"(project(":androidApp:baselineprofile"))
|
||||
"baselineProfile"(projects.androidApp.baselineprofile)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.kgurgul.cpuinfo.appinitializers
|
||||
|
||||
import android.app.Application
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class AppInitializerComponent : KoinComponent {
|
||||
|
||||
private val nativeToolsInitializer: NativeToolsInitializer by inject()
|
||||
|
||||
fun init(application: Application) {
|
||||
nativeToolsInitializer.init(application)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.kgurgul.cpuinfo.appinitializers
|
||||
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Module
|
||||
@ComponentScan
|
||||
class AppInitializersModule
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.kgurgul.cpuinfo.appinitializers
|
||||
|
||||
import android.app.Application
|
||||
import com.getkeepsafe.relinker.ReLinker
|
||||
import com.kgurgul.cpuinfo.data.provider.CpuDataNativeProvider
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
@Single
|
||||
class NativeToolsInitializer(
|
||||
private val cpuDataNativeProvider: CpuDataNativeProvider
|
||||
) {
|
||||
|
||||
fun init(application: Application) {
|
||||
ReLinker.loadLibrary(application, "cpuinfo-libs")
|
||||
cpuDataNativeProvider.initLibrary()
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.kgurgul.cpuinfo.ui.components
|
||||
|
||||
/*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarItemColors
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
import androidx.compose.material3.NavigationRailItemColors
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun CpuNavigationSuiteScaffold(
|
||||
navigationSuiteItems: NavigationSuiteScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
val navigationSuitColors = NavigationSuiteDefaults.colors(
|
||||
navigationBarContainerColor = MaterialTheme.colorScheme.primary,
|
||||
navigationBarContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
navigationRailContainerColor = MaterialTheme.colorScheme.primary,
|
||||
navigationRailContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
NavigationSuiteScaffold(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
navigationSuiteColors = navigationSuitColors,
|
||||
navigationSuiteItems = navigationSuiteItems,
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
object CpuNavigationSuiteScaffoldDefault {
|
||||
@Composable
|
||||
fun itemDefaultColors() = NavigationSuiteItemColors(
|
||||
navigationBarItemColors = NavigationBarItemColors(
|
||||
selectedIconColor = MaterialTheme.colorScheme.onPrimary,
|
||||
selectedTextColor = MaterialTheme.colorScheme.onPrimary,
|
||||
selectedIndicatorColor = MaterialTheme.colorScheme.secondary,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
unselectedTextColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
disabledIconColor = MaterialTheme.colorScheme.onPrimary,
|
||||
disabledTextColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
navigationRailItemColors = NavigationRailItemColors(
|
||||
selectedIconColor = MaterialTheme.colorScheme.onPrimary,
|
||||
selectedTextColor = MaterialTheme.colorScheme.onPrimary,
|
||||
selectedIndicatorColor = MaterialTheme.colorScheme.secondary,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
unselectedTextColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
disabledIconColor = MaterialTheme.colorScheme.onPrimary,
|
||||
disabledTextColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
navigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
|
||||
)
|
||||
}*/
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.core
|
||||
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
@@ -6,6 +21,5 @@ import org.junit.Rule
|
||||
|
||||
abstract class BaseTestSuit {
|
||||
|
||||
@get:Rule
|
||||
val androidComposeRule = createAndroidComposeRule<HostActivity>()
|
||||
}
|
||||
@get:Rule val androidComposeRule = createAndroidComposeRule<HostActivity>()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017 KG Soft
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kgurgul.cpuinfo.core
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||
@@ -144,8 +143,6 @@ fun SemanticsNodeInteraction.isNotDisplayed(): Boolean {
|
||||
}
|
||||
|
||||
fun SemanticsNodeInteractionCollection.hasAnyElement(): Boolean {
|
||||
val matchedNodes = fetchSemanticsNodes(
|
||||
atLeastOneRootRequired = true,
|
||||
)
|
||||
val matchedNodes = fetchSemanticsNodes(atLeastOneRootRequired = true)
|
||||
return matchedNodes.isNotEmpty()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017 KG Soft
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,16 +13,25 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kgurgul.cpuinfo.pages
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.cpu
|
||||
import com.kgurgul.cpuinfo.ui.components.TEST_TAG_CPU_TOP_APP_BAR
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class InfoContainerPage(
|
||||
private val composeContent: ComposeContentTestRule,
|
||||
) {
|
||||
class InfoContainerPage(composeContent: ComposeContentTestRule) {
|
||||
|
||||
private val toolbarTitle = composeContent.onNodeWithTag(TEST_TAG_CPU_TOP_APP_BAR)
|
||||
private val cpuTab = composeContent.onNodeWithText(runBlocking { getString(Res.string.cpu) })
|
||||
|
||||
fun assertViewDisplayed() {
|
||||
|
||||
toolbarTitle.assertIsDisplayed()
|
||||
cpuTab.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2017 KG Soft
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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
|
||||
* 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,
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kgurgul.cpuinfo.testsuits.information
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
@@ -28,6 +27,7 @@ class InfoTestSuit : BaseTestSuit() {
|
||||
private val infoContainerPage = InfoContainerPage(androidComposeRule)
|
||||
|
||||
@Test
|
||||
fun checkInfoTabs() {
|
||||
fun checkInitialScreenDisplayed() {
|
||||
infoContainerPage.assertViewDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.uitestutils.conditionwatcher
|
||||
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
|
||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
|
||||
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".CpuInfoApp"
|
||||
android:allowBackup="true"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:extractNativeLibs="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:resizeableActivity="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/AppThemeBase"
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<activity
|
||||
android:name="com.kgurgul.cpuinfo.HostActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Launcher"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="kgurgul.com" />
|
||||
<data android:pathPattern="/assets/cpuinfo.*" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.kgurgul.cpuinfo.tv.TvActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Launcher"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".features.cputile.CpuTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_cpu_low"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 KG Soft
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,17 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kgurgul.cpuinfo
|
||||
|
||||
import android.app.Application
|
||||
import com.kgurgul.cpuinfo.appinitializers.AppInitializerComponent
|
||||
import com.kgurgul.cpuinfo.appinitializers.AppInitializersModule
|
||||
import com.kgurgul.cpuinfo.di.androidModule
|
||||
import com.kgurgul.cpuinfo.di.sharedModule
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.ksp.generated.module
|
||||
|
||||
class CpuInfoApp : Application() {
|
||||
|
||||
@@ -31,12 +28,8 @@ class CpuInfoApp : Application() {
|
||||
super.onCreate()
|
||||
startKoin {
|
||||
androidContext(this@CpuInfoApp)
|
||||
modules(
|
||||
androidModule,
|
||||
AppInitializersModule().module,
|
||||
sharedModule,
|
||||
)
|
||||
modules(androidModule, sharedModule)
|
||||
}
|
||||
AppInitializerComponent().init(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kgurgul.cpuinfo.features.HostScreen
|
||||
import com.kgurgul.cpuinfo.features.HostViewModel
|
||||
import com.kgurgul.cpuinfo.ui.shouldUseDarkTheme
|
||||
import com.kgurgul.cpuinfo.ui.theme.CpuInfoTheme
|
||||
import com.kgurgul.cpuinfo.ui.theme.DarkColors
|
||||
import com.kgurgul.cpuinfo.ui.theme.LightColors
|
||||
import com.kgurgul.cpuinfo.ui.theme.darkPrimary
|
||||
import com.kgurgul.cpuinfo.ui.theme.lightPrimary
|
||||
import com.kgurgul.cpuinfo.utils.AndroidShortcutManager
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HostActivity : ComponentActivity() {
|
||||
|
||||
private val viewModel: HostViewModel by viewModel()
|
||||
private val androidShortcutManager: AndroidShortcutManager by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val splashScreen = installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
var uiState: HostViewModel.UiState by mutableStateOf(HostViewModel.UiState())
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.uiStateFlow.onEach { uiState = it }.collect()
|
||||
}
|
||||
}
|
||||
splashScreen.setKeepOnScreenCondition { uiState.isLoading }
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val darkTheme = shouldUseDarkTheme(uiState)
|
||||
val systemBarScrim = (if (darkTheme) darkPrimary else lightPrimary).toArgb()
|
||||
DisposableEffect(darkTheme) {
|
||||
enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.dark(systemBarScrim),
|
||||
navigationBarStyle = SystemBarStyle.dark(systemBarScrim),
|
||||
)
|
||||
onDispose {}
|
||||
}
|
||||
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
val colors =
|
||||
when {
|
||||
dynamicColor && darkTheme -> {
|
||||
val dynamicDarkColors = dynamicDarkColorScheme(LocalContext.current)
|
||||
DarkColors.copy(
|
||||
secondary = dynamicDarkColors.secondaryFixed,
|
||||
onSecondary = dynamicDarkColors.onSecondaryFixed,
|
||||
tertiaryFixed = dynamicDarkColors.tertiaryFixed,
|
||||
onTertiaryFixed = dynamicDarkColors.onTertiaryFixed,
|
||||
)
|
||||
}
|
||||
|
||||
dynamicColor && !darkTheme -> {
|
||||
val dynamicLightColors = dynamicLightColorScheme(LocalContext.current)
|
||||
LightColors.copy(
|
||||
secondary = dynamicLightColors.secondaryFixed,
|
||||
onSecondary = dynamicLightColors.onSecondaryFixed,
|
||||
tertiaryFixed = dynamicLightColors.tertiaryFixed,
|
||||
onTertiaryFixed = dynamicLightColors.onTertiaryFixed,
|
||||
)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColors
|
||||
else -> LightColors
|
||||
}
|
||||
LaunchedEffect(uiState.isApplicationSectionVisible) {
|
||||
androidShortcutManager.createShortcuts(uiState.isApplicationSectionVisible)
|
||||
}
|
||||
CpuInfoTheme(useDarkTheme = darkTheme, colors = colors) {
|
||||
HostScreen(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.features.cputile
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
@@ -7,15 +22,17 @@ import androidx.annotation.RequiresApi
|
||||
import com.kgurgul.cpuinfo.R
|
||||
import com.kgurgul.cpuinfo.data.provider.CpuDataProvider
|
||||
import com.kgurgul.cpuinfo.utils.IDispatchersProvider
|
||||
import com.kgurgul.cpuinfo.utils.formatHz
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
@@ -23,14 +40,13 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
private val cpuDataProvider: CpuDataProvider by inject()
|
||||
private val dispatchersProvider: IDispatchersProvider by inject()
|
||||
|
||||
private val job = SupervisorJob()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + dispatchersProvider.main
|
||||
get() = SupervisorJob() + dispatchersProvider.main
|
||||
|
||||
private var refreshingJob: Job? = null
|
||||
|
||||
private val minMaxAvg: Pair<Long, Long> by lazy {
|
||||
val cpuCount = cpuDataProvider.getNumberOfCores()
|
||||
val cpuCount = cpuDataProvider.getNumberOfLogicalCores()
|
||||
val minFreq = mutableListOf<Long>()
|
||||
val maxFreq = mutableListOf<Long>()
|
||||
for (i in 0 until cpuCount) {
|
||||
@@ -45,7 +61,7 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
mapOf(
|
||||
CPULoad.Low to Icon.createWithResource(this, R.drawable.ic_cpu_low),
|
||||
CPULoad.Medium to Icon.createWithResource(this, R.drawable.ic_cpu_med),
|
||||
CPULoad.High to Icon.createWithResource(this, R.drawable.ic_cpu_high)
|
||||
CPULoad.High to Icon.createWithResource(this, R.drawable.ic_cpu_high),
|
||||
)
|
||||
}
|
||||
private val defaultIcon by lazy { Icon.createWithResource(this, R.drawable.ic_cpu_high) }
|
||||
@@ -55,8 +71,8 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
refreshingJob?.cancel()
|
||||
refreshingJob = launch {
|
||||
while (true) {
|
||||
val load = withContext(dispatchersProvider.io) { averageCPUFreq() }
|
||||
qsTile.label = "Avg ${load}MHz"
|
||||
val load = getAverageCPUFreq()
|
||||
qsTile.label = "Avg ${formatHz(load)}"
|
||||
qsTile.icon = getLoadIcon(load)
|
||||
qsTile.updateTile()
|
||||
delay(REFRESHING_DELAY_MS)
|
||||
@@ -70,37 +86,42 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
job.cancel()
|
||||
coroutineContext.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun getLoadIcon(avgLoad: Long): Icon {
|
||||
val freqDiff = minMaxAvg.second - minMaxAvg.first
|
||||
val freqThirds = freqDiff / 3
|
||||
val loadEnum = when {
|
||||
avgLoad >= minMaxAvg.second - freqThirds -> CPULoad.High
|
||||
minMaxAvg.second - freqThirds > avgLoad && avgLoad >= minMaxAvg.first + freqThirds -> CPULoad.Medium
|
||||
else -> CPULoad.Low
|
||||
}
|
||||
val loadEnum =
|
||||
when {
|
||||
avgLoad >= minMaxAvg.second - freqThirds -> CPULoad.High
|
||||
minMaxAvg.second - freqThirds > avgLoad &&
|
||||
avgLoad >= minMaxAvg.first + freqThirds -> CPULoad.Medium
|
||||
|
||||
else -> CPULoad.Low
|
||||
}
|
||||
return icons.getOrDefault(loadEnum, defaultIcon)
|
||||
}
|
||||
|
||||
private fun averageCPUFreq(): Long {
|
||||
val cpuCount = cpuDataProvider.getNumberOfCores()
|
||||
var sumFreq = 0L
|
||||
for (i in 0 until cpuCount) {
|
||||
sumFreq += cpuDataProvider.getCurrentFreq(i)
|
||||
private suspend fun getAverageCPUFreq(): Long {
|
||||
return withContext(dispatchersProvider.io) {
|
||||
val cpuCount = cpuDataProvider.getNumberOfLogicalCores()
|
||||
var sumFreq = 0L
|
||||
for (i in 0 until cpuCount) {
|
||||
sumFreq += cpuDataProvider.getCurrentFreq(i)
|
||||
}
|
||||
sumFreq / cpuCount
|
||||
}
|
||||
return (sumFreq / cpuCount)
|
||||
}
|
||||
|
||||
enum class CPULoad {
|
||||
Low,
|
||||
Medium,
|
||||
High
|
||||
High,
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REFRESHING_DELAY_MS = 1000L
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,25 @@
|
||||
package com.kgurgul.cpuinfo
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -14,8 +29,8 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kgurgul.cpuinfo.features.HostScreen
|
||||
import com.kgurgul.cpuinfo.features.HostViewModel
|
||||
import com.kgurgul.cpuinfo.tv.features.TvHostScreen
|
||||
import com.kgurgul.cpuinfo.ui.shouldUseDarkTheme
|
||||
import com.kgurgul.cpuinfo.ui.theme.CpuInfoTheme
|
||||
import com.kgurgul.cpuinfo.ui.theme.darkPrimary
|
||||
@@ -25,7 +40,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HostActivity : AppCompatActivity() {
|
||||
class TvActivity : ComponentActivity() {
|
||||
|
||||
private val viewModel: HostViewModel by viewModel()
|
||||
|
||||
@@ -36,9 +51,7 @@ class HostActivity : AppCompatActivity() {
|
||||
var uiState: HostViewModel.UiState by mutableStateOf(HostViewModel.UiState())
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.uiStateFlow
|
||||
.onEach { uiState = it }
|
||||
.collect()
|
||||
viewModel.uiStateFlow.onEach { uiState = it }.collect()
|
||||
}
|
||||
}
|
||||
splashScreen.setKeepOnScreenCondition { uiState.isLoading }
|
||||
@@ -55,13 +68,7 @@ class HostActivity : AppCompatActivity() {
|
||||
)
|
||||
onDispose {}
|
||||
}
|
||||
CpuInfoTheme(
|
||||
useDarkTheme = darkTheme,
|
||||
) {
|
||||
HostScreen(
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
CpuInfoTheme(useDarkTheme = darkTheme) { TvHostScreen(viewModel = viewModel) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features
|
||||
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.NavigationDrawer
|
||||
import androidx.tv.material3.NavigationDrawerItem
|
||||
import androidx.tv.material3.NavigationDrawerItemDefaults
|
||||
import com.kgurgul.cpuinfo.features.HostViewModel
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.applications
|
||||
import com.kgurgul.cpuinfo.shared.hardware
|
||||
import com.kgurgul.cpuinfo.shared.ic_android
|
||||
import com.kgurgul.cpuinfo.shared.ic_cpu
|
||||
import com.kgurgul.cpuinfo.shared.ic_settings
|
||||
import com.kgurgul.cpuinfo.shared.ic_temperature
|
||||
import com.kgurgul.cpuinfo.shared.settings
|
||||
import com.kgurgul.cpuinfo.shared.temp
|
||||
import com.kgurgul.cpuinfo.tv.features.applications.TvApplicationsBaseRoute
|
||||
import com.kgurgul.cpuinfo.tv.features.applications.tvApplicationsScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.TvInformationBaseRoute
|
||||
import com.kgurgul.cpuinfo.tv.features.information.tvInformationScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.settings.TvSettingsBaseRoute
|
||||
import com.kgurgul.cpuinfo.tv.features.settings.tvSettingsScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.temperature.TvTemperaturesBaseRoute
|
||||
import com.kgurgul.cpuinfo.tv.features.temperature.tvTemperaturesScreen
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingMedium
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.navigation.TopLevelRoute
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvHostScreen(viewModel: HostViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvHostScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvHostScreen(uiState: HostViewModel.UiState) {
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
NavigationDrawer(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
drawerContent = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight().padding(spacingMedium).selectableGroup(),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall, Alignment.CenterVertically),
|
||||
) {
|
||||
buildTopLevelRoutes(isApplicationsVisible = uiState.isApplicationSectionVisible)
|
||||
.forEach { topLevelRoute ->
|
||||
NavigationDrawerItem(
|
||||
selected =
|
||||
currentDestination?.hierarchy?.any {
|
||||
it.hasRoute(topLevelRoute.route::class)
|
||||
} == true,
|
||||
onClick = {
|
||||
navController.navigate(topLevelRoute.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
leadingContent = {
|
||||
Icon(
|
||||
painter = painterResource(topLevelRoute.icon),
|
||||
contentDescription = stringResource(topLevelRoute.name),
|
||||
)
|
||||
},
|
||||
colors =
|
||||
NavigationDrawerItemDefaults.colors(
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceTint,
|
||||
focusedContentColor = MaterialTheme.colorScheme.onBackground,
|
||||
selectedContentColor = MaterialTheme.colorScheme.onBackground,
|
||||
selectedContainerColor =
|
||||
MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.4f),
|
||||
pressedContentColor = MaterialTheme.colorScheme.onBackground,
|
||||
pressedContainerColor =
|
||||
MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.4f),
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(topLevelRoute.name),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = TvInformationBaseRoute,
|
||||
enterTransition = { fadeIn() },
|
||||
exitTransition = { fadeOut() },
|
||||
popEnterTransition = { fadeIn() },
|
||||
popExitTransition = { fadeOut() },
|
||||
) {
|
||||
tvInformationScreen()
|
||||
tvApplicationsScreen()
|
||||
tvTemperaturesScreen()
|
||||
tvSettingsScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildTopLevelRoutes(isApplicationsVisible: Boolean) = buildList {
|
||||
add(
|
||||
TopLevelRoute(
|
||||
name = Res.string.hardware,
|
||||
route = TvInformationBaseRoute,
|
||||
icon = Res.drawable.ic_cpu,
|
||||
)
|
||||
)
|
||||
if (isApplicationsVisible) {
|
||||
add(
|
||||
TopLevelRoute(
|
||||
name = Res.string.applications,
|
||||
route = TvApplicationsBaseRoute,
|
||||
icon = Res.drawable.ic_android,
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
TopLevelRoute(
|
||||
name = Res.string.temp,
|
||||
route = TvTemperaturesBaseRoute,
|
||||
icon = Res.drawable.ic_temperature,
|
||||
)
|
||||
)
|
||||
add(
|
||||
TopLevelRoute(
|
||||
name = Res.string.settings,
|
||||
route = TvSettingsBaseRoute,
|
||||
icon = Res.drawable.ic_settings,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.applications
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.tv.material3.Icon
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.LocalPlatformContext
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.kgurgul.cpuinfo.domain.model.ExtendedApplicationData
|
||||
import com.kgurgul.cpuinfo.features.applications.ApplicationsScreenTestData
|
||||
import com.kgurgul.cpuinfo.features.applications.ApplicationsViewModel
|
||||
import com.kgurgul.cpuinfo.features.applications.registerUninstallListener
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.apps_open
|
||||
import com.kgurgul.cpuinfo.shared.apps_settings
|
||||
import com.kgurgul.cpuinfo.shared.apps_show_system_apps
|
||||
import com.kgurgul.cpuinfo.shared.apps_sort_order
|
||||
import com.kgurgul.cpuinfo.shared.apps_uninstall
|
||||
import com.kgurgul.cpuinfo.shared.ic_apk_document_filled
|
||||
import com.kgurgul.cpuinfo.shared.ic_apk_document_outlined
|
||||
import com.kgurgul.cpuinfo.shared.ok
|
||||
import com.kgurgul.cpuinfo.shared.refresh
|
||||
import com.kgurgul.cpuinfo.shared.search
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvAlertDialog
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvButton
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvIconButton
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvWideButton
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuPullToRefreshBox
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuSnackbar
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuTextField
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingMedium
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingXSmall
|
||||
import com.kgurgul.cpuinfo.utils.navigation.NavigationConst
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Serializable
|
||||
data object TvApplicationsBaseRoute {
|
||||
|
||||
@Serializable data object TvApplicationsRoute
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.tvApplicationsScreen() {
|
||||
navigation<TvApplicationsBaseRoute>(
|
||||
startDestination = TvApplicationsBaseRoute.TvApplicationsRoute,
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<TvApplicationsBaseRoute>(
|
||||
basePath = NavigationConst.BASE_URL + NavigationConst.APPLICATIONS
|
||||
)
|
||||
),
|
||||
) {
|
||||
composable<TvApplicationsBaseRoute.TvApplicationsRoute> { TvApplicationsScreen() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvApplicationsScreen(viewModel: ApplicationsViewModel = koinViewModel()) {
|
||||
registerUninstallListener(onRefresh = viewModel::onRefreshApplications)
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
val searchQuery by viewModel.searchQuery.collectAsStateWithLifecycle()
|
||||
TvApplicationsScreen(
|
||||
uiState = uiState,
|
||||
onAppClicked = viewModel::onApplicationClicked,
|
||||
onRefreshApplications = viewModel::onRefreshApplications,
|
||||
onSnackbarDismissed = viewModel::onSnackbarDismissed,
|
||||
onAppUninstallClicked = viewModel::onAppUninstallClicked,
|
||||
onAppSettingsClicked = viewModel::onAppSettingsClicked,
|
||||
onSystemAppsSwitched = viewModel::onSystemAppsSwitched,
|
||||
onSortOrderChange = viewModel::onSortOrderChange,
|
||||
searchQuery = searchQuery,
|
||||
onSearchQueryChanged = viewModel::onSearchQueryChanged,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TvApplicationsScreen(
|
||||
uiState: ApplicationsViewModel.UiState,
|
||||
onAppClicked: (packageName: String) -> Unit,
|
||||
onRefreshApplications: () -> Unit,
|
||||
onSnackbarDismissed: () -> Unit,
|
||||
onAppUninstallClicked: (id: String) -> Unit,
|
||||
onAppSettingsClicked: (id: String) -> Unit,
|
||||
onSystemAppsSwitched: (enabled: Boolean) -> Unit,
|
||||
onSortOrderChange: (ascending: Boolean) -> Unit,
|
||||
searchQuery: String,
|
||||
onSearchQueryChanged: (String) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val snackbarMessageString = uiState.snackbarMessage?.let { stringResource(it) }
|
||||
LaunchedEffect(snackbarMessageString) {
|
||||
scope.launch {
|
||||
if (snackbarMessageString != null) {
|
||||
val result = snackbarHostState.showSnackbar(snackbarMessageString)
|
||||
if (result == SnackbarResult.Dismissed) {
|
||||
onSnackbarDismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var optionsDialogId by remember { mutableStateOf("") }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
withSystemApps = uiState.withSystemApps,
|
||||
onRefreshApplications = onRefreshApplications,
|
||||
onSystemAppsSwitched = onSystemAppsSwitched,
|
||||
isSortAscending = uiState.isSortAscending,
|
||||
onSortOrderChange = onSortOrderChange,
|
||||
searchQuery = searchQuery,
|
||||
onSearchQueryChanged = onSearchQueryChanged,
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal),
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) { data -> CpuSnackbar(data) } },
|
||||
) { innerPaddingModifier ->
|
||||
CpuPullToRefreshBox(
|
||||
isRefreshing = uiState.isLoading,
|
||||
enabled = false,
|
||||
onRefresh = { onRefreshApplications() },
|
||||
modifier = Modifier.fillMaxSize().padding(innerPaddingModifier),
|
||||
) {
|
||||
ApplicationsList(
|
||||
appList = uiState.applications,
|
||||
onAppClicked = { optionsDialogId = it },
|
||||
)
|
||||
}
|
||||
OptionsDialog(
|
||||
isVisible = optionsDialogId.isNotEmpty(),
|
||||
onDismissRequest = { optionsDialogId = "" },
|
||||
appId = optionsDialogId,
|
||||
onOpenAppClicked = onAppClicked,
|
||||
onSettingsClicked = onAppSettingsClicked,
|
||||
onUninstallAppClicked = onAppUninstallClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
withSystemApps: Boolean,
|
||||
onRefreshApplications: () -> Unit,
|
||||
onSystemAppsSwitched: (enabled: Boolean) -> Unit,
|
||||
isSortAscending: Boolean,
|
||||
onSortOrderChange: (ascending: Boolean) -> Unit,
|
||||
searchQuery: String,
|
||||
onSearchQueryChanged: (String) -> Unit,
|
||||
) {
|
||||
var showSearch by rememberSaveable { mutableStateOf(false) }
|
||||
BackHandler(enabled = showSearch) { showSearch = false }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(spacingMedium, Alignment.End),
|
||||
modifier = Modifier.fillMaxWidth().padding(spacingMedium),
|
||||
) {
|
||||
AnimatedVisibility(visible = showSearch) {
|
||||
SearchTextField(
|
||||
searchQuery = searchQuery,
|
||||
onSearchQueryChanged = onSearchQueryChanged,
|
||||
onSearchClosed = { showSearch = false },
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = !showSearch) {
|
||||
TvIconButton(onClick = { showSearch = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(Res.string.search),
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(visible = !showSearch) {
|
||||
TvIconButton(onClick = onRefreshApplications) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Refresh,
|
||||
contentDescription = stringResource(Res.string.refresh),
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(visible = !showSearch) {
|
||||
TvIconButton(onClick = { onSortOrderChange(!isSortAscending) }) {
|
||||
val icon =
|
||||
if (isSortAscending) {
|
||||
Icons.Default.KeyboardArrowDown
|
||||
} else {
|
||||
Icons.Default.KeyboardArrowUp
|
||||
}
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = stringResource(Res.string.apps_sort_order),
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(visible = !showSearch) {
|
||||
val systemAppIcon =
|
||||
if (withSystemApps) {
|
||||
Res.drawable.ic_apk_document_filled
|
||||
} else {
|
||||
Res.drawable.ic_apk_document_outlined
|
||||
}
|
||||
TvIconButton(onClick = { onSystemAppsSwitched(!withSystemApps) }) {
|
||||
Icon(
|
||||
painter = painterResource(systemAppIcon),
|
||||
contentDescription = stringResource(Res.string.apps_show_system_apps),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplicationsList(
|
||||
appList: ImmutableList<ExtendedApplicationData>,
|
||||
onAppClicked: (packageName: String) -> Unit,
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(spacingMedium)) {
|
||||
items(items = appList, key = { item -> item.packageName }) { item ->
|
||||
TvListItem(
|
||||
onClick = { onAppClicked(item.packageName) },
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
Row { ApplicationItem(appData = item, modifier = Modifier.weight(1f)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplicationItem(appData: ExtendedApplicationData, modifier: Modifier = Modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||
AsyncImage(
|
||||
model =
|
||||
ImageRequest.Builder(LocalPlatformContext.current)
|
||||
.data(appData.appIconUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = appData.name,
|
||||
modifier = Modifier.size(50.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(spacingMedium))
|
||||
SelectionContainer {
|
||||
Column(modifier = Modifier.padding(horizontal = spacingXSmall)) {
|
||||
Text(
|
||||
text = appData.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(
|
||||
text = appData.packageName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(
|
||||
text = appData.versionName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptionsDialog(
|
||||
isVisible: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
appId: String,
|
||||
onOpenAppClicked: (id: String) -> Unit,
|
||||
onSettingsClicked: (id: String) -> Unit,
|
||||
onUninstallAppClicked: (id: String) -> Unit,
|
||||
) {
|
||||
if (isVisible) {
|
||||
TvAlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
text = {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(horizontal = spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
) {
|
||||
item(key = "__open") {
|
||||
TvWideButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onOpenAppClicked(appId)
|
||||
},
|
||||
title = { Text(text = stringResource(Res.string.apps_open)) },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item(key = "__settings") {
|
||||
TvWideButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onSettingsClicked(appId)
|
||||
},
|
||||
title = { Text(text = stringResource(Res.string.apps_settings)) },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Settings,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item(key = "__uninstall") {
|
||||
TvWideButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onUninstallAppClicked(appId)
|
||||
},
|
||||
title = { Text(text = stringResource(Res.string.apps_uninstall)) },
|
||||
icon = {
|
||||
Icon(imageVector = Icons.Default.Delete, contentDescription = null)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TvButton(onClick = onDismissRequest) {
|
||||
androidx.tv.material3.Text(text = stringResource(Res.string.ok))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchTextField(
|
||||
searchQuery: String,
|
||||
onSearchQueryChanged: (String) -> Unit,
|
||||
onSearchClosed: () -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
val onSearchExplicitlyTriggered = {
|
||||
keyboardController?.hide()
|
||||
onSearchClosed()
|
||||
}
|
||||
|
||||
CpuTextField(
|
||||
colors =
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
cursorColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(Res.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
},
|
||||
onValueChange = { if ("\n" !in it) onSearchQueryChanged(it) },
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
|
||||
.padding(spacingSmall)
|
||||
.focusRequester(focusRequester)
|
||||
.onKeyEvent {
|
||||
if (it.key == Key.Enter) {
|
||||
if (searchQuery.isBlank()) return@onKeyEvent false
|
||||
onSearchExplicitlyTriggered()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
.testTag(ApplicationsScreenTestData.SEARCH_TEST_TAG),
|
||||
shape = RoundedCornerShape(32.dp),
|
||||
value = searchQuery,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions =
|
||||
KeyboardActions(
|
||||
onSearch = {
|
||||
if (searchQuery.isBlank()) return@KeyboardActions
|
||||
onSearchExplicitlyTriggered()
|
||||
}
|
||||
),
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
)
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@file:OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
||||
|
||||
package com.kgurgul.cpuinfo.tv.features.information
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.tv.material3.Tab
|
||||
import androidx.tv.material3.TabRow
|
||||
import androidx.tv.material3.TabRowDefaults
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.ANDROID_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.CPU_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.GPU_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.HARDWARE_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.INFO_PAGE_AMOUNT
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.RAM_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.SCREEN_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.SENSORS_POS
|
||||
import com.kgurgul.cpuinfo.features.information.InfoContainerViewModel.Companion.STORAGE_POS
|
||||
import com.kgurgul.cpuinfo.features.information.getTabTitle
|
||||
import com.kgurgul.cpuinfo.tv.features.information.cpu.TvCpuInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.gpu.TvGpuInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.hardware.TvHardwareInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.os.TvOsInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.ram.TvRamInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.screen.TvScreenInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.sensors.TvSensorsInfoScreen
|
||||
import com.kgurgul.cpuinfo.tv.features.information.storage.TvStorageInfoScreen
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingMedium
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.navigation.NavigationConst
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Serializable
|
||||
data object TvInformationBaseRoute {
|
||||
|
||||
@Serializable object TvInformationRoute
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.tvInformationScreen() {
|
||||
navigation<TvInformationBaseRoute>(
|
||||
startDestination = TvInformationBaseRoute.TvInformationRoute,
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<TvInformationBaseRoute>(
|
||||
basePath = NavigationConst.BASE_URL + NavigationConst.INFORMATION
|
||||
)
|
||||
),
|
||||
) {
|
||||
composable<TvInformationBaseRoute.TvInformationRoute> { TvInfoContainerScreen() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvInfoContainerScreen(viewModel: InfoContainerViewModel = koinViewModel()) {
|
||||
TvInfoContainerScreen(onPageChanged = viewModel::onPageChanged)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvInfoContainerScreen(onPageChanged: (Int) -> Unit) {
|
||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) {
|
||||
paddingValues ->
|
||||
InfoContainer(
|
||||
paddingValues = paddingValues,
|
||||
onPageChanged = onPageChanged,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoContainer(
|
||||
paddingValues: PaddingValues,
|
||||
onPageChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tabTitles = (0 until INFO_PAGE_AMOUNT).map { getTabTitle(position = it) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
|
||||
Column(modifier = Modifier.padding(top = paddingValues.calculateTopPadding()).then(modifier)) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
indicator = { tabPositions, doesTabRowHaveFocus ->
|
||||
TabRowDefaults.PillIndicator(
|
||||
currentTabPosition = tabPositions[selectedTabIndex],
|
||||
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||
activeColor = MaterialTheme.colorScheme.surfaceTint,
|
||||
inactiveColor = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.4f),
|
||||
)
|
||||
},
|
||||
modifier =
|
||||
Modifier.align(Alignment.CenterHorizontally).focusRestorer().padding(spacingMedium),
|
||||
) {
|
||||
tabTitles.forEachIndexed { index, tab ->
|
||||
key(index) {
|
||||
Tab(
|
||||
selected = index == selectedTabIndex,
|
||||
onFocus = { selectedTabIndex = index },
|
||||
) {
|
||||
Text(
|
||||
text = tab,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
horizontal = spacingMedium,
|
||||
vertical = spacingSmall,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedContent(
|
||||
targetState = selectedTabIndex,
|
||||
label = "screen_transition",
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(220, delayMillis = 90))
|
||||
.togetherWith(fadeOut(animationSpec = tween(90)))
|
||||
},
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
start = paddingValues.calculateStartPadding(LayoutDirection.Ltr),
|
||||
end = paddingValues.calculateEndPadding(LayoutDirection.Ltr),
|
||||
),
|
||||
) { targetPos ->
|
||||
when (targetPos) {
|
||||
CPU_POS -> TvCpuInfoScreen()
|
||||
GPU_POS -> TvGpuInfoScreen()
|
||||
RAM_POS -> TvRamInfoScreen()
|
||||
STORAGE_POS -> TvStorageInfoScreen()
|
||||
SCREEN_POS -> TvScreenInfoScreen()
|
||||
ANDROID_POS -> TvOsInfoScreen()
|
||||
HARDWARE_POS -> TvHardwareInfoScreen()
|
||||
SENSORS_POS -> TvSensorsInfoScreen()
|
||||
else -> throw IllegalArgumentException("Unknown position")
|
||||
}
|
||||
onPageChanged(targetPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.cpu
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.CpuData
|
||||
import com.kgurgul.cpuinfo.domain.model.getName
|
||||
import com.kgurgul.cpuinfo.domain.model.getValue
|
||||
import com.kgurgul.cpuinfo.features.information.base.InformationRow
|
||||
import com.kgurgul.cpuinfo.features.information.cpu.CpuInfoViewModel
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.cpu_current_frequency
|
||||
import com.kgurgul.cpuinfo.shared.cpu_frequency_stopped
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuProgressBar
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.formatHz
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvCpuInfoScreen(viewModel: CpuInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvCpuInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvCpuInfoScreen(uiState: CpuInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize().testTag(TvCpuInfoScreenTestTags.LAZY_COLUMN),
|
||||
) {
|
||||
uiState.cpuData?.let { cpuData ->
|
||||
cpuData.frequencies.forEachIndexed { i, frequency ->
|
||||
item(key = "__frequency_$i") {
|
||||
TvListItem { FrequencyItem(index = i, frequency = frequency) }
|
||||
}
|
||||
}
|
||||
items(cpuData.cpuItems) { itemValue ->
|
||||
TvListItem {
|
||||
InformationRow(
|
||||
title = itemValue.getName(),
|
||||
value = itemValue.getValue(),
|
||||
isLastItem = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FrequencyItem(index: Int, frequency: CpuData.Frequency) {
|
||||
val currentFreq =
|
||||
if (frequency.current != -1L) {
|
||||
stringResource(Res.string.cpu_current_frequency, index, formatHz(frequency.current))
|
||||
} else {
|
||||
stringResource(Res.string.cpu_frequency_stopped, index)
|
||||
}
|
||||
val minFreq =
|
||||
if (frequency.min != -1L) {
|
||||
formatHz(0)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val maxFreq =
|
||||
if (frequency.max != -1L) {
|
||||
formatHz(frequency.max)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val progress =
|
||||
if (frequency.current != -1L && frequency.max != 0L) {
|
||||
frequency.current.toFloat() / frequency.max.toFloat()
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
CpuProgressBar(label = currentFreq, progress = progress, minMaxValues = minFreq to maxFreq)
|
||||
}
|
||||
|
||||
object TvCpuInfoScreenTestTags {
|
||||
const val LAZY_COLUMN = "cpu_info_lazy_column"
|
||||
const val SOCKET_NAME = "cpu_info_socket_name"
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.gpu
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.getKey
|
||||
import com.kgurgul.cpuinfo.domain.model.getName
|
||||
import com.kgurgul.cpuinfo.domain.model.getValue
|
||||
import com.kgurgul.cpuinfo.features.information.base.InformationRow
|
||||
import com.kgurgul.cpuinfo.features.information.gpu.GpuInfoViewModel
|
||||
import com.kgurgul.cpuinfo.features.information.gpu.InternalGLSurfaceView
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvGpuInfoScreen(viewModel: GpuInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
Box {
|
||||
InternalGLSurfaceView(
|
||||
onGlInfoReceived = { vendor, renderer, extensions ->
|
||||
viewModel.onGlInfoReceived(vendor, renderer, extensions)
|
||||
}
|
||||
)
|
||||
TvGpuInfoScreen(uiState = uiState)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvGpuInfoScreen(uiState: GpuInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.gpuData, key = { itemValue -> itemValue.getKey() }) { itemValue ->
|
||||
TvListItem {
|
||||
InformationRow(
|
||||
title = itemValue.getName(),
|
||||
value = itemValue.getValue(),
|
||||
isLastItem = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.hardware
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.getName
|
||||
import com.kgurgul.cpuinfo.domain.model.getValue
|
||||
import com.kgurgul.cpuinfo.features.information.base.InformationRow
|
||||
import com.kgurgul.cpuinfo.features.information.hardware.HardwareInfoViewModel
|
||||
import com.kgurgul.cpuinfo.features.information.hardware.registerPowerPlugListener
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvHardwareInfoScreen(viewModel: HardwareInfoViewModel = koinViewModel()) {
|
||||
registerPowerPlugListener(onRefresh = viewModel::refreshHardwareInfo)
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvHardwareInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvHardwareInfoScreen(uiState: HardwareInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.hardwareItems) { itemValue ->
|
||||
TvListItem {
|
||||
InformationRow(
|
||||
title = itemValue.getName(),
|
||||
value = itemValue.getValue(),
|
||||
isLastItem = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.os
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.getKey
|
||||
import com.kgurgul.cpuinfo.domain.model.getName
|
||||
import com.kgurgul.cpuinfo.domain.model.getValue
|
||||
import com.kgurgul.cpuinfo.features.information.base.InformationRow
|
||||
import com.kgurgul.cpuinfo.features.information.os.OsInfoViewModel
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvOsInfoScreen(viewModel: OsInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvOsInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvOsInfoScreen(uiState: OsInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.items, key = { itemValue -> itemValue.getKey() }) { itemValue ->
|
||||
TvListItem {
|
||||
InformationRow(
|
||||
title = itemValue.getName(),
|
||||
value = itemValue.getValue(),
|
||||
isLastItem = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.ram
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.features.information.ram.RamInfoViewModel
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.available_memory
|
||||
import com.kgurgul.cpuinfo.shared.threshold
|
||||
import com.kgurgul.cpuinfo.shared.total_memory
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.components.ItemValueRow
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.Utils
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvRamInfoScreen(viewModel: RamInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvRamInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvRamInfoScreen(uiState: RamInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
uiState.ramData?.let { ramData ->
|
||||
item(key = "__total") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.total_memory),
|
||||
value = Utils.convertBytesToMega(ramData.total),
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "__available") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.available_memory),
|
||||
value =
|
||||
"${Utils.convertBytesToMega(ramData.available)} " +
|
||||
"(${ramData.availablePercentage}%)",
|
||||
)
|
||||
}
|
||||
}
|
||||
if (ramData.threshold != -1L) {
|
||||
item(key = "__threshold") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.threshold),
|
||||
value = Utils.convertBytesToMega(ramData.threshold),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.getKey
|
||||
import com.kgurgul.cpuinfo.domain.model.getName
|
||||
import com.kgurgul.cpuinfo.domain.model.getValue
|
||||
import com.kgurgul.cpuinfo.features.information.base.InformationRow
|
||||
import com.kgurgul.cpuinfo.features.information.screen.ScreenInfoViewModel
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvScreenInfoScreen(viewModel: ScreenInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvScreenInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvScreenInfoScreen(uiState: ScreenInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.items, key = { itemValue -> itemValue.getKey() }) { itemValue ->
|
||||
TvListItem {
|
||||
InformationRow(
|
||||
title = itemValue.getName(),
|
||||
value = itemValue.getValue(),
|
||||
isLastItem = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.sensors
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.asString
|
||||
import com.kgurgul.cpuinfo.features.information.sensors.SensorsInfoViewModel
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingXSmall
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvSensorsInfoScreen(viewModel: SensorsInfoViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvSensorsInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvSensorsInfoScreen(uiState: SensorsInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.sensors, key = { sensorData -> sensorData.id }) { sensorData ->
|
||||
TvListItem { SensorItem(title = sensorData.name.asString(), value = sensorData.value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SensorItem(title: String, value: String) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(spacingXSmall))
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = .7f),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.information.storage
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.asString
|
||||
import com.kgurgul.cpuinfo.features.information.storage.StorageInfoViewModel
|
||||
import com.kgurgul.cpuinfo.features.information.storage.registerStorageMountingListener
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuProgressBar
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.Utils
|
||||
import kotlin.math.roundToInt
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun TvStorageInfoScreen(viewModel: StorageInfoViewModel = koinViewModel()) {
|
||||
registerStorageMountingListener(viewModel::onRefreshStorage)
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvStorageInfoScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvStorageInfoScreen(uiState: StorageInfoViewModel.UiState) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(uiState.storageItems, key = { it.id }) { storageItem ->
|
||||
val label = buildString {
|
||||
val notFormattedLabel = storageItem.label.asString().trim()
|
||||
if (notFormattedLabel.isNotEmpty()) {
|
||||
append(notFormattedLabel)
|
||||
append(": ")
|
||||
}
|
||||
}
|
||||
val totalReadable = Utils.humanReadableByteCount(storageItem.storageTotal)
|
||||
val usedReadable = Utils.humanReadableByteCount(storageItem.storageUsed)
|
||||
val progress =
|
||||
if (storageItem.storageTotal != 0L)
|
||||
storageItem.storageUsed.toFloat() / storageItem.storageTotal.toFloat()
|
||||
else 0f
|
||||
val usedPercent = (progress * 100.0).roundToInt()
|
||||
val storageDesc = "$label$usedReadable / $totalReadable ($usedPercent%)"
|
||||
val minMaxValues =
|
||||
Utils.humanReadableByteCount(0) to
|
||||
Utils.humanReadableByteCount(storageItem.storageTotal)
|
||||
TvListItem {
|
||||
CpuProgressBar(
|
||||
label = storageDesc,
|
||||
progress = progress,
|
||||
progressHeight = 32.dp,
|
||||
prefixImageRes = storageItem.iconDrawable,
|
||||
minMaxValues = minMaxValues,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.RadioButtonDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.kgurgul.cpuinfo.features.settings.SettingsViewModel
|
||||
import com.kgurgul.cpuinfo.features.settings.getTemperatureUnit
|
||||
import com.kgurgul.cpuinfo.features.settings.getThemeName
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.cancel
|
||||
import com.kgurgul.cpuinfo.shared.general
|
||||
import com.kgurgul.cpuinfo.shared.pref_theme
|
||||
import com.kgurgul.cpuinfo.shared.pref_theme_choose
|
||||
import com.kgurgul.cpuinfo.shared.temperature_unit
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvAlertDialog
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvButton
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingLarge
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingMedium
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.navigation.NavigationConst
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Serializable
|
||||
data object TvSettingsBaseRoute {
|
||||
|
||||
@Serializable data object TvSettingsRoute
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.tvSettingsScreen() {
|
||||
navigation<TvSettingsBaseRoute>(
|
||||
startDestination = TvSettingsBaseRoute.TvSettingsRoute,
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<TvSettingsBaseRoute>(
|
||||
basePath = NavigationConst.BASE_URL + NavigationConst.SETTINGS
|
||||
)
|
||||
),
|
||||
) {
|
||||
composable<TvSettingsBaseRoute.TvSettingsRoute> { TvSettingsScreen() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvSettingsScreen(viewModel: SettingsViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvSettingsScreen(
|
||||
uiState = uiState,
|
||||
onTemperatureOptionClicked = viewModel::setTemperatureUnit,
|
||||
onThemeOptionClicked = viewModel::setTheme,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvSettingsScreen(
|
||||
uiState: SettingsViewModel.UiState,
|
||||
onTemperatureOptionClicked: (Int) -> Unit,
|
||||
onThemeOptionClicked: (String) -> Unit,
|
||||
) {
|
||||
Scaffold { paddingValues ->
|
||||
var isTemperatureDialogVisible by remember { mutableStateOf(false) }
|
||||
var isThemeDialogVisible by remember { mutableStateOf(false) }
|
||||
SettingsList(
|
||||
uiState = uiState,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
onThemeItemClicked = { isThemeDialogVisible = true },
|
||||
onTemperatureItemClicked = { isTemperatureDialogVisible = true },
|
||||
)
|
||||
TemperatureUnitDialog(
|
||||
isDialogVisible = isTemperatureDialogVisible,
|
||||
onDismissRequest = { isTemperatureDialogVisible = false },
|
||||
currentSelection = uiState.temperatureUnit,
|
||||
options = uiState.temperatureDialogOptions,
|
||||
onOptionClicked = onTemperatureOptionClicked,
|
||||
)
|
||||
ThemeDialog(
|
||||
isDialogVisible = isThemeDialogVisible,
|
||||
onDismissRequest = { isThemeDialogVisible = false },
|
||||
currentSelection = uiState.theme,
|
||||
options = uiState.themeDialogOptions,
|
||||
onOptionClicked = onThemeOptionClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsList(
|
||||
uiState: SettingsViewModel.UiState,
|
||||
onThemeItemClicked: () -> Unit,
|
||||
onTemperatureItemClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(contentPadding = PaddingValues(spacingMedium), modifier = modifier) {
|
||||
item(key = "__generalHeader") {
|
||||
Text(
|
||||
text = stringResource(Res.string.general),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
Spacer(modifier = Modifier.requiredSize(spacingMedium))
|
||||
}
|
||||
item(key = "__themeItem") {
|
||||
SettingsItem(
|
||||
title = stringResource(Res.string.pref_theme),
|
||||
subtitle = getThemeName(option = uiState.theme),
|
||||
onClick = onThemeItemClicked,
|
||||
)
|
||||
}
|
||||
item(key = "__temperatureItem") {
|
||||
SettingsItem(
|
||||
title = stringResource(Res.string.temperature_unit),
|
||||
subtitle = getTemperatureUnit(option = uiState.temperatureUnit),
|
||||
onClick = onTemperatureItemClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsItem(title: String, subtitle: String, onClick: () -> Unit) {
|
||||
TvListItem(onClick = onClick) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(vertical = spacingMedium)
|
||||
.padding(start = spacingLarge)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TemperatureUnitDialog(
|
||||
isDialogVisible: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
currentSelection: Int,
|
||||
options: ImmutableList<Int>,
|
||||
onOptionClicked: (Int) -> Unit,
|
||||
) {
|
||||
if (isDialogVisible) {
|
||||
TvAlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = stringResource(Res.string.temperature_unit)) },
|
||||
text = {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.verticalScroll(scrollState),
|
||||
) {
|
||||
for (option in options) {
|
||||
TvListItem(
|
||||
onClick = {
|
||||
onOptionClicked(option)
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(spacingMedium),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = spacingSmall),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = option == currentSelection,
|
||||
onClick = null,
|
||||
colors =
|
||||
RadioButtonDefaults.colors(
|
||||
selectedColor = MaterialTheme.colorScheme.tertiary
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = getTemperatureUnit(option = option),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TvButton(onClick = onDismissRequest) {
|
||||
androidx.tv.material3.Text(text = stringResource(Res.string.cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemeDialog(
|
||||
isDialogVisible: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
currentSelection: String,
|
||||
options: ImmutableList<String>,
|
||||
onOptionClicked: (String) -> Unit,
|
||||
) {
|
||||
if (isDialogVisible) {
|
||||
TvAlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = stringResource(Res.string.pref_theme_choose)) },
|
||||
text = {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.verticalScroll(scrollState),
|
||||
) {
|
||||
for (option in options) {
|
||||
TvListItem(
|
||||
onClick = {
|
||||
onOptionClicked(option)
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(spacingMedium),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = spacingSmall),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = option == currentSelection,
|
||||
onClick = null,
|
||||
colors =
|
||||
RadioButtonDefaults.colors(
|
||||
selectedColor = MaterialTheme.colorScheme.tertiary
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = getThemeName(option = option),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TvButton(onClick = onDismissRequest) {
|
||||
androidx.tv.material3.Text(text = stringResource(Res.string.cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.features.temperature
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.kgurgul.cpuinfo.domain.model.TemperatureItem
|
||||
import com.kgurgul.cpuinfo.domain.model.asString
|
||||
import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter
|
||||
import com.kgurgul.cpuinfo.features.temperature.TemperatureViewModel
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.no_temp_data
|
||||
import com.kgurgul.cpuinfo.shared.no_temp_data_admin_required
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingMedium
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
import com.kgurgul.cpuinfo.utils.navigation.NavigationConst
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Serializable
|
||||
data object TvTemperaturesBaseRoute {
|
||||
|
||||
@Serializable data object TvTemperaturesRoute
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.tvTemperaturesScreen() {
|
||||
navigation<TvTemperaturesBaseRoute>(
|
||||
startDestination = TvTemperaturesBaseRoute.TvTemperaturesRoute,
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<TvTemperaturesBaseRoute>(
|
||||
basePath = NavigationConst.BASE_URL + NavigationConst.TEMPERATURES
|
||||
)
|
||||
),
|
||||
) {
|
||||
composable<TvTemperaturesBaseRoute.TvTemperaturesRoute> { TvTemperatureScreen() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvTemperatureScreen(viewModel: TemperatureViewModel = koinViewModel()) {
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
TvTemperatureScreen(uiState = uiState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvTemperatureScreen(uiState: TemperatureViewModel.UiState) {
|
||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) {
|
||||
paddingValues ->
|
||||
val paddingModifier = Modifier.padding(paddingValues)
|
||||
if (uiState.temperatureItems.isEmpty()) {
|
||||
TvEmptyTemperatureList(
|
||||
isAdminRequired = uiState.isAdminRequired,
|
||||
modifier = paddingModifier,
|
||||
)
|
||||
} else {
|
||||
TvTemperatureList(
|
||||
temperatureItems = uiState.temperatureItems,
|
||||
temperatureFormatter = uiState.temperatureFormatter,
|
||||
modifier = paddingModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvTemperatureList(
|
||||
temperatureItems: ImmutableList<TemperatureItem>,
|
||||
temperatureFormatter: TemperatureFormatter,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(spacingMedium).then(modifier)) {
|
||||
items(items = temperatureItems, key = { item -> item.id }) { item ->
|
||||
TvTemperatureItem(item = item, temperatureFormatter = temperatureFormatter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvTemperatureItem(item: TemperatureItem, temperatureFormatter: TemperatureFormatter) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
TvListItem {
|
||||
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
|
||||
Icon(
|
||||
painter = painterResource(item.icon),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.requiredSize(60.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.requiredSize(spacingSmall))
|
||||
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
|
||||
Text(
|
||||
text = item.name.asString(),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Spacer(modifier = Modifier.requiredSize(spacingSmall))
|
||||
if (!item.temperature.isNaN()) {
|
||||
var formattedTemp by remember { mutableStateOf("") }
|
||||
LaunchedEffect(item.temperature) {
|
||||
coroutineScope.launch {
|
||||
formattedTemp = temperatureFormatter.format(item.temperature)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = formattedTemp,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvEmptyTemperatureList(isAdminRequired: Boolean, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize().padding(spacingMedium).then(modifier),
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
if (isAdminRequired) {
|
||||
Res.string.no_temp_data_admin_required
|
||||
} else {
|
||||
Res.string.no_temp_data
|
||||
}
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.ui.components
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun TvAlertDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
confirmButton: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable (() -> Unit)? = null,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = title,
|
||||
tonalElevation = 0.dp,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
text = text,
|
||||
confirmButton = confirmButton,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.tv.material3.Button
|
||||
import androidx.tv.material3.ButtonDefaults
|
||||
import androidx.tv.material3.IconButton
|
||||
import androidx.tv.material3.IconButtonDefaults
|
||||
import androidx.tv.material3.OutlinedIconButton
|
||||
import androidx.tv.material3.OutlinedIconButtonDefaults
|
||||
import androidx.tv.material3.WideButton
|
||||
import androidx.tv.material3.WideButtonDefaults
|
||||
|
||||
@Composable
|
||||
fun TvIconButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
colors =
|
||||
IconButtonDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.2f),
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.5f),
|
||||
focusedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvOutlinedIconButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
OutlinedIconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
colors =
|
||||
OutlinedIconButtonDefaults.colors(
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedContentColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
colors =
|
||||
ButtonDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.7f),
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surfaceTint,
|
||||
focusedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TvWideButton(
|
||||
onClick: () -> Unit,
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: (@Composable () -> Unit)? = null,
|
||||
subtitle: (@Composable () -> Unit)? = null,
|
||||
enabled: Boolean = true,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
WideButton(
|
||||
onClick = onClick,
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
icon = icon,
|
||||
contentColor =
|
||||
WideButtonDefaults.contentColor(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
focusedColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
scale = WideButtonDefaults.scale(focusedScale = 1.05f),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource,
|
||||
background = { TvWideButtonBackground(enabled, interactionSource) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvWideButtonBackground(enabled: Boolean, interactionSource: MutableInteractionSource) {
|
||||
val isFocused = interactionSource.collectIsFocusedAsState().value
|
||||
val isPressed = interactionSource.collectIsPressedAsState().value
|
||||
|
||||
val backgroundColor =
|
||||
when {
|
||||
!enabled -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.01f)
|
||||
isPressed -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
isFocused -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
|
||||
else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().background(backgroundColor))
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo.tv.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.tv.material3.DenseListItem
|
||||
import androidx.tv.material3.ListItemColors
|
||||
import androidx.tv.material3.ListItemDefaults
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
|
||||
@Composable
|
||||
fun TvListItem(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
colors: ListItemColors =
|
||||
ListItemDefaults.colors(
|
||||
focusedContainerColor = MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.4f)
|
||||
),
|
||||
headlineContent: @Composable () -> Unit,
|
||||
) {
|
||||
DenseListItem(
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
scale = ListItemDefaults.scale(focusedScale = 1.01f),
|
||||
colors = colors,
|
||||
headlineContent = headlineContent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.6666667"
|
||||
android:scaleY="0.6666667"
|
||||
android:translateX="18"
|
||||
android:translateY="18">
|
||||
<group
|
||||
android:scaleX="0.5625"
|
||||
android:translateX="23.625">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m62.72,30l1.08,0a0.42,0.43 90.01,0 0,-0.01 0.07q0,0.95 0,1.9a0.63,0.63 90,0 0,0.01 0.09l0.68,0l0,4.3l2.14,0l0,-4.3l0.68,0c0,-0.04 0.01,-0.07 0.01,-0.1q0,-0.94 0,-1.88A0.57,0.57 89.99,0 0,67.3 30l1.08,0l0,2.06l0.69,0l0,4.31c0.2,0 0.37,-0.01 0.55,0 0.44,0.04 0.88,-0.1 1.32,0.08a0.63,0.63 90,0 1,0.29 0.18,0.69 0.69,89.99 0,0 0.12,0.12 0.37,0.37 90,0 1,0.18 0.29,0.11 0.11,90.01 0,0 0.02,0.08c0.1,0.05 0.07,0.14 0.07,0.22 0,0.58 0,1.15 0,1.73 0,0.05 0,0.09 0,0.15l4.31,0l0,0.68l0.18,0 1.89,0l0,1.08l-2.07,0l0,0.69l-4.3,0l0,1.96l4.3,0l0,0.67l2.06,0l0,1.08l-2.07,0l0,0.69l-4.3,0l0,2.25l4.3,0l0,0.68l2.06,0l0,1.08l-2.07,0l0,0.69l-4.3,0l0,2.06l4.3,0l0,0.68l2.06,0l0,1.08l-2.07,0l0,0.69l-4.3,0l0,2.25l4.3,0l0,0.68l2.06,0l0,1.08l-2.06,0l0,0.69l-4.3,0l0,2.06l4.31,0l0,0.69l0.16,0 1.89,0l0,1.06c-0.05,0 -0.09,0.01 -0.14,0.01l-1.93,0l0,0.69l-4.3,0l0,2.35l4.31,0l0,0.68l0.16,0 1.89,0l0,1.08l-2.07,0l0,0.69l-4.31,0c0,0.05 0,0.1 0,0.14 0,0.43 0,0.85 0,1.28a0.49,0.49 90.01,0 1,-0.03 0.14,1.06 1.06,90 0,1 -0.06,0.13 1.11,1.11 90,0 1,-0.13 0.25,2.23 2.23,90 0,1 -0.27,0.22 1.45,1.45 89.99,0 1,-0.45 0.18l-1.47,0 -0.14,0l0,4.31l-0.69,0c0,0.07 0,0.12 0,0.17q0,0.94 0,1.89l-1.06,0c0,-0.05 -0.01,-0.09 -0.01,-0.14q0,-0.89 0,-1.77l0,-0.15l-0.69,0l0,-4.26l-2.25,0l0,4.31l-0.69,0c0,0.07 0,0.12 0,0.17q0,0.94 0,1.89l-1.06,0c0,-0.05 -0.01,-0.09 -0.01,-0.14q0,-0.89 0,-1.77l0,-0.15l-0.69,0l0,-4.3l-2.06,0l0,4.3l-0.67,0l0,2.06l-1.08,0l0,-2.07l-0.69,0l0,-4.3l-2.15,0l0,4.31l-0.68,0l0,2.06l-1.08,0l0,-2.06l-0.69,0l0,-4.3l-2.15,0l0,4.31l-0.69,0l0,2.06l-1.08,0l0,-2.06l-0.69,0l0,-4.3l-2.15,0l0,4.31l-0.68,0c0,0.07 0,0.12 0,0.17q0,0.94 0,1.89l-1.08,0l0,-2.07l-0.69,0l0,-4.29l-2.15,0l0,4.31l-0.69,0c0,0.06 0,0.12 0,0.17q0,0.95 0,1.89l-1.09,0c0,-0.05 -0.01,-0.1 -0.01,-0.14q0,-0.89 0,-1.77l0,-0.15l-0.69,0l0,-4.31l-0.14,0c-0.52,0 -1.03,0.01 -1.55,-0.01a1.64,1.64 89.99,0 1,-0.41 -0.12,0.11 0.11,90 0,1 -0.03,-0.03c-0.05,-0.05 -0.1,-0.1 -0.15,-0.14 -0.13,-0.1 -0.27,-0.21 -0.27,-0.4a0.09,0.09 89.98,0 0,-0.02 -0.06c-0.08,-0.03 -0.07,-0.1 -0.07,-0.16 0,-0.5 0,-0.99 0,-1.49 0,-0.04 0,-0.08 0,-0.14l-4.31,0l0,-0.69l-0.16,0 -1.9,0l0,-1.08a0.42,0.43 90.01,0 0,0.07 0.01l1.9,0a0.61,0.61 89.99,0 0,0.09 -0.02l0,-0.68l4.3,0l0,-1.96l-4.3,0l0,-0.69l-2.06,0L30,62.91l2.06,0l0,-0.69l4.3,0l0,-2.25l-4.3,0l0,-0.69l-2.06,0l0,-1.08l2.06,0l0,-0.69l4.3,0l0,-2.06l-4.3,0l0,-0.68l-0.15,0 -1.9,0l0,-1.08l2.06,0l0,-0.69l4.3,0l0,-2.25l-4.3,0l0,-0.68l-0.15,0 -1.9,0l0,-1.08l2.06,0l0,-0.69l4.3,0l0,-2.06l-4.3,0L32.06,45.58c-0.04,0 -0.07,-0.01 -0.1,-0.01l-1.88,0a0.57,0.57 89.99,0 0,-0.08 0.01l0,-1.08l2.06,0L32.06,43.81l4.3,0l0,-2.35l-4.3,0l0,-0.69l-0.15,0 -1.9,0l0,-1.08a0.42,0.43 90.01,0 0,0.07 0.01l1.9,0a0.62,0.62 90,0 0,0.09 -0.02l0,-0.69l4.3,0c0,-0.06 0,-0.1 0,-0.14q0,-0.79 0,-1.59a0.36,0.36 90,0 1,0.02 -0.12c0.02,-0.05 0.05,-0.1 0.07,-0.14a0.29,0.29 89.99,0 1,0.03 -0.11c0.11,-0.07 0.14,-0.2 0.25,-0.27 0.11,-0.07 0.18,-0.17 0.32,-0.17a0.08,0.08 90,0 0,0.06 -0.02c0.04,-0.11 0.14,-0.07 0.21,-0.07 0.51,0 1.03,0 1.54,0l0.14,0l0,-4.31l0.68,0c0,-0.04 0.01,-0.07 0.01,-0.1q0,-0.94 0,-1.88a0.57,0.57 89.99,0 0,-0.01 -0.08l1.08,0l0,2.06l0.69,0l0,4.3l2.25,0l0,-4.3l0.68,0c0,-0.04 0.01,-0.07 0.01,-0.1q0,-0.94 0,-1.88a0.57,0.57 89.99,0 0,-0.01 -0.08l1.08,0l0,2.06l0.7,0l0,4.3l2.05,0l0,-4.3l0.68,0l0,-2.06l1.08,0l0,2.06l0.69,0l0,4.3l2.15,0l0,-4.3l0.69,0l0,-2.06l1.08,0l0,2.06l0.69,0l0,4.3l2.15,0l0,-4.3l0.68,0c0,-0.06 0,-0.1 0,-0.15q0,-0.95 0,-1.9l1.08,0l0,2.06l0.69,0l0,4.3l2.15,0l0,-4.3l0.69,0c0,-0.06 0,-0.11 0,-0.15q0.03,-0.95 0.03,-1.9zM38.63,53.91l0,0q0,7.15 0,14.3a1.29,1.29 90,0 0,0.06 0.47,0.88 0.88,89.99 0,0 0.25,0.37 0.85,0.85 89.99,0 0,0.57 0.22c0.06,0 0.13,0 0.19,0q14.09,0 28.18,0c0.27,0 0.53,0.05 0.81,-0.07a0.93,0.93 90,0 0,0.46 -0.38c0.08,-0.13 0.16,-0.29 0.13,-0.47a0.7,0.7 89.99,0 1,0 -0.13q0,-13.92 -0,-27.85c0,-0.39 0.09,-0.79 -0.08,-1.18a0.67,0.67 90.01,0 0,-0.32 -0.37,0.23 0.23,89.99 0,1 -0.05,-0.02 0.57,0.57 89.99,0 0,-0.39 -0.16l-26.61,0c-0.81,0 -1.62,0 -2.43,0a1.36,1.36 90,0 0,-0.36 0.1,0.05 0.05,90.01 0,0 -0.01,0.02 0.69,0.69 89.99,0 1,-0.12 0.15c-0.1,0.07 -0.18,0.14 -0.18,0.27a0.04,0.04 90,0 1,-0.02 0.03c-0.09,0.02 -0.06,0.1 -0.07,0.15 0,0.1 0,0.2 0,0.31q-0,7.13 -0,14.25z"
|
||||
android:strokeWidth="0.1" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m39.95,54q0,-6.25 0,-12.49c0,-0.12 0,-0.24 0.01,-0.35a0.5,0.5 90.01,0 1,0.06 -0.14,0.11 0.11,90.01 0,0 0.02,-0.04c-0.01,-0.2 0.11,-0.35 0.22,-0.48a1.27,1.27 90,0 1,0.43 -0.36c0.1,-0.05 0.22,-0.05 0.29,-0.1 0.17,-0.13 0.36,-0.08 0.53,-0.08q9.11,-0.01 18.23,0l6.78,0c0.11,0 0.23,0 0.34,0.01a0.44,0.44 90.01,0 1,0.13 0.06,0.11 0.11,90.01 0,0 0.04,0.02c0.2,-0.02 0.33,0.13 0.49,0.22a0.7,0.7 89.99,0 1,0.14 0.15c0.03,0.04 0.07,0.07 0.1,0.11 0.06,0.08 0.12,0.16 0.18,0.25a0.02,0.02 90.01,0 1,0 0.01c0,0.19 0.12,0.34 0.12,0.53 -0.01,1.23 -0,2.45 0,3.68q0,10.72 0,21.45c0,0.11 0,0.23 0,0.34 0,0.08 0.02,0.17 -0.07,0.23a0.11,0.11 90.01,0 0,-0.01 0.05,1.08 1.08,90 0,1 -0.29,0.52 3.01,3.01 89.99,0 0,-0.24 0.24,0.03 0.03,90 0,1 -0.01,0.01l-0.32,0.14c-0.05,0.02 -0.1,0.04 -0.14,0.06a0.42,0.42 90,0 1,-0.12 0.03c-0.11,0 -0.22,0 -0.33,0l-25.05,0c-0.07,0 -0.14,0 -0.21,0 -0.1,-0.01 -0.2,0.03 -0.27,-0.08a0.07,0.07 89.99,0 0,-0.05 -0.01,0.77 0.77,89.99 0,1 -0.48,-0.23 1.27,1.27 90,0 1,-0.35 -0.43c-0.04,-0.1 -0.04,-0.21 -0.1,-0.28 -0.13,-0.16 -0.08,-0.33 -0.08,-0.5q-0.01,-6.27 -0,-12.53zM66.38,65.89c0,-0.24 -0.24,-0.47 -0.49,-0.49 -0.2,-0.01 -0.49,0.29 -0.49,0.49 0,0.2 0.29,0.49 0.49,0.49 0.2,0 0.53,-0.29 0.49,-0.49z"
|
||||
android:strokeWidth="0.1" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background" />
|
||||
<foreground android:drawable="@drawable/ic_banner_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,24 +1,38 @@
|
||||
import com.diffplug.gradle.spotless.SpotlessExtension
|
||||
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
|
||||
import com.kgurgul.DependencyUpdates
|
||||
import com.kgurgul.ReleaseType
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.dependencyUpdate)
|
||||
alias(libs.plugins.kotlin.multiplatform) apply false
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.ksp) apply false
|
||||
alias(libs.plugins.androidTest) apply false
|
||||
alias(libs.plugins.baselineprofile) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
alias(libs.plugins.compose.multiplatform) apply false
|
||||
alias(libs.plugins.serialization) apply false
|
||||
alias(libs.plugins.spotless)
|
||||
}
|
||||
|
||||
configure<SpotlessExtension> {
|
||||
kotlin {
|
||||
target("**/*.kt")
|
||||
targetExclude("**/build/**/*.kt", "spotless/**/*.kt")
|
||||
ktfmt(libs.ktfmt.get().version).kotlinlangStyle()
|
||||
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.withType<KotlinCompile>().all {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +42,9 @@ subprojects {
|
||||
* current version.
|
||||
*/
|
||||
tasks.withType<DependencyUpdatesTask> {
|
||||
doFirst {
|
||||
gradle.startParameter.isParallelProjectExecutionEnabled = false
|
||||
}
|
||||
rejectVersionIf {
|
||||
val current = DependencyUpdates.versionToRelease(currentVersion)
|
||||
// If we're using a SNAPSHOT, ignore since we must be doing so for a reason.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
object KoverConfig {
|
||||
|
||||
val includedPackages = listOf("com.kgurgul.cpuinfo.*")
|
||||
val excludedPackages = listOf(
|
||||
"com.kgurgul.cpuinfo.widgets",
|
||||
"com.kgurgul.cpuinfo.ui",
|
||||
"com.kgurgul.cpuinfo.*.model*",
|
||||
"com.kgurgul.cpuinfo.*.models*",
|
||||
)
|
||||
val excludedClasses = listOf(
|
||||
"*Module*",
|
||||
"com.kgurgul.cpuinfo.*Directions*",
|
||||
"*MembersInjector*",
|
||||
"*_Companion_*",
|
||||
"*_Factory*",
|
||||
)
|
||||
val excludedAnnotations = "*Generated*"
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
object SigningConfig {
|
||||
|
||||
const val KEY_PATH = "KEYSTORE_PATH"
|
||||
const val KEY_PASS = "KEYSTORE_PASS"
|
||||
const val KEY_ALIAS = "KEYSTORE_ALIAS"
|
||||
|
||||
fun getDebugProperties(rootDir: File) = Properties().apply {
|
||||
setProperty(KEY_PATH, "${rootDir.path}/androidApp/debug.keystore")
|
||||
setProperty(KEY_PASS, "android")
|
||||
setProperty(KEY_ALIAS, "androiddebugkey")
|
||||
setProperty(KEY_PASS, "android")
|
||||
}
|
||||
|
||||
fun getReleaseProperties(rootDir: File): Properties {
|
||||
val releaseProperties = Properties()
|
||||
try {
|
||||
releaseProperties.load(FileInputStream(File(rootDir, "local.properties")))
|
||||
} catch (e: Exception) {
|
||||
println("Cannot load local.properties")
|
||||
}
|
||||
return if (releaseProperties.getProperty(KEY_PATH, "").isNotEmpty()
|
||||
&& releaseProperties.getProperty(KEY_PASS, "").isNotEmpty()
|
||||
&& releaseProperties.getProperty(KEY_ALIAS, "").isNotEmpty()
|
||||
) {
|
||||
println("Using local.properties for signing")
|
||||
releaseProperties
|
||||
} else if (!System.getenv(KEY_PATH).isNullOrEmpty()
|
||||
&& !System.getenv(KEY_PASS).isNullOrEmpty()
|
||||
&& !System.getenv(KEY_ALIAS).isNullOrEmpty()
|
||||
) {
|
||||
println("Using system env variables for signing")
|
||||
releaseProperties[KEY_PATH] = System.getenv(KEY_PATH)
|
||||
releaseProperties[KEY_PASS] = System.getenv(KEY_PASS)
|
||||
releaseProperties[KEY_ALIAS] = System.getenv(KEY_ALIAS)
|
||||
releaseProperties
|
||||
} else {
|
||||
println("!!!Warning: release keystore not found -> using debug!!!")
|
||||
getDebugProperties(rootDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
object Versions {
|
||||
|
||||
const val MIN_SDK = 21
|
||||
const val TARGET_SDK = 34
|
||||
const val COMPILE_SDK = 34
|
||||
|
||||
private const val VERSION_MAJOR = 5
|
||||
private const val VERSION_MINOR = 5
|
||||
private const val VERSION_PATCH = 0
|
||||
|
||||
const val VERSION_CODE = VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH
|
||||
const val VERSION_NAME = "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH"
|
||||
|
||||
const val NDK_VERSION = "25.1.8937393"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
object AndroidSigningConfig {
|
||||
|
||||
const val KEY_PATH = "KEYSTORE_PATH"
|
||||
const val KEY_PASS = "KEYSTORE_PASS"
|
||||
const val KEY_ALIAS = "KEYSTORE_ALIAS"
|
||||
|
||||
fun getDebugProperties(rootDir: File) =
|
||||
Properties().apply {
|
||||
setProperty(KEY_PATH, "${rootDir.path}/androidApp/debug.keystore")
|
||||
setProperty(KEY_PASS, "android")
|
||||
setProperty(KEY_ALIAS, "androiddebugkey")
|
||||
setProperty(KEY_PASS, "android")
|
||||
}
|
||||
|
||||
fun getReleaseProperties(rootDir: File): Properties {
|
||||
val releaseProperties = Properties()
|
||||
try {
|
||||
releaseProperties.load(FileInputStream(File(rootDir, "local.properties")))
|
||||
} catch (e: Exception) {
|
||||
println("Cannot load local.properties")
|
||||
}
|
||||
return if (
|
||||
releaseProperties.getProperty(KEY_PATH, "").isNotEmpty() &&
|
||||
releaseProperties.getProperty(KEY_PASS, "").isNotEmpty() &&
|
||||
releaseProperties.getProperty(KEY_ALIAS, "").isNotEmpty()
|
||||
) {
|
||||
println("Using local.properties for signing")
|
||||
releaseProperties
|
||||
} else if (
|
||||
!System.getenv(KEY_PATH).isNullOrEmpty() &&
|
||||
!System.getenv(KEY_PASS).isNullOrEmpty() &&
|
||||
!System.getenv(KEY_ALIAS).isNullOrEmpty()
|
||||
) {
|
||||
println("Using system env variables for signing")
|
||||
releaseProperties[KEY_PATH] = System.getenv(KEY_PATH)
|
||||
releaseProperties[KEY_PASS] = System.getenv(KEY_PASS)
|
||||
releaseProperties[KEY_ALIAS] = System.getenv(KEY_ALIAS)
|
||||
releaseProperties
|
||||
} else {
|
||||
println("!!!Warning: release keystore not found -> using debug!!!")
|
||||
getDebugProperties(rootDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul
|
||||
|
||||
object AndroidVersions {
|
||||
|
||||
const val MIN_SDK = 23
|
||||
const val WEAR_MIN_SDK = 26
|
||||
const val TARGET_SDK = 36
|
||||
const val COMPILE_SDK = 36
|
||||
|
||||
private const val VERSION_MAJOR = 6
|
||||
private const val VERSION_MINOR = 6
|
||||
private const val VERSION_PATCH = 1
|
||||
|
||||
const val VERSION_CODE = VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH
|
||||
const val WEAR_VERSION_CODE =
|
||||
1000000 + VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH
|
||||
const val VERSION_NAME = "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH"
|
||||
|
||||
const val NDK_VERSION = "28.2.13676358"
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul
|
||||
|
||||
enum class ReleaseType(private val level: Int) {
|
||||
SNAPSHOT(0),
|
||||
DEV(1),
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul
|
||||
|
||||
object KoverConfig {
|
||||
|
||||
val includedPackages = listOf("com.kgurgul.cpuinfo.*")
|
||||
val excludedPackages =
|
||||
listOf(
|
||||
"com.kgurgul.cpuinfo.widgets",
|
||||
"com.kgurgul.cpuinfo.ui",
|
||||
"com.kgurgul.cpuinfo.*.model*",
|
||||
"com.kgurgul.cpuinfo.*.models*",
|
||||
)
|
||||
val excludedClasses =
|
||||
listOf(
|
||||
"*Module*",
|
||||
"com.kgurgul.cpuinfo.*Directions*",
|
||||
"*MembersInjector*",
|
||||
"*_Companion_*",
|
||||
"*_Factory*",
|
||||
)
|
||||
val excludedAnnotations = "*Generated*"
|
||||
}
|
||||
@@ -11,7 +11,7 @@ kotlin {
|
||||
sourceSets {
|
||||
jvmMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(project(":shared"))
|
||||
implementation(projects.shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,12 @@ compose.desktop {
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Pkg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
|
||||
packageName = "CPU-Info"
|
||||
packageVersion = "1.3.0"
|
||||
packageVersion = "1.10.0"
|
||||
|
||||
modules("jdk.unsupported")
|
||||
modules("jdk.unsupported.desktop")
|
||||
|
||||
val iconsRoot = project.file("desktop-icons")
|
||||
macOS {
|
||||
@@ -60,8 +64,10 @@ compose.desktop {
|
||||
}
|
||||
|
||||
buildTypes.release.proguard {
|
||||
isEnabled = false
|
||||
isEnabled = true
|
||||
optimize = true
|
||||
obfuscate = true
|
||||
configurationFiles.from("rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,56 @@
|
||||
-keep class kotlinx.coroutines.** { *; }
|
||||
-dontwarn kotlinx.atomicfu.**
|
||||
-dontwarn org.slf4j.**
|
||||
-dontnote "module-info"
|
||||
-dontnote "META-INF**"
|
||||
|
||||
# Obfuscation breaks coroutines/ktor for some reason
|
||||
-dontobfuscate
|
||||
# DataStore
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
-dontwarn androidx.datastore.preferences.**
|
||||
|
||||
# JNA - keep all classes and suppress reflection notes
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontnote com.sun.jna.**
|
||||
|
||||
# WindowsExeIconDecoder - JNA interfaces must be kept with original names
|
||||
-keep class com.kgurgul.cpuinfo.utils.WindowsExeIconDecoder { *; }
|
||||
-keep class com.kgurgul.cpuinfo.utils.WindowsExeIconDecoder$* { *; }
|
||||
|
||||
# MacOSIconDecoder - Coil decoder for .icns files, loaded dynamically
|
||||
-keep class com.kgurgul.cpuinfo.utils.MacOSIconDecoder { *; }
|
||||
-keep class com.kgurgul.cpuinfo.utils.MacOSIconDecoder$* { *; }
|
||||
|
||||
# OSHI
|
||||
-keep class oshi.** { *; }
|
||||
-dontnote oshi.**
|
||||
# OSHI optional dependency (not included, but dynamically referenced)
|
||||
-dontwarn io.github.pandalxb.jlibrehardwaremonitor.**
|
||||
|
||||
# Okio
|
||||
-keep class okio.** { *; }
|
||||
|
||||
# Kotlin annotations
|
||||
-dontwarn kotlin.Deprecated$Container
|
||||
|
||||
# Compose Material3 Adaptive
|
||||
-dontwarn androidx.compose.material3.adaptive.navigationsuite.**
|
||||
|
||||
# SLF4J
|
||||
-dontnote org.slf4j.**
|
||||
|
||||
# TwelveMonkeys - ImageIO plugin for ICNS files, loaded via Java SPI
|
||||
-keep class com.twelvemonkeys.** { *; }
|
||||
-dontnote com.twelvemonkeys.**
|
||||
|
||||
# Java ImageIO SPI - keep service provider implementations
|
||||
-keep class javax.imageio.** { *; }
|
||||
-keepclassmembers class * implements javax.imageio.spi.ImageReaderSpi { *; }
|
||||
-keepclassmembers class * implements javax.imageio.spi.ImageWriterSpi { *; }
|
||||
-keepclassmembers class * extends javax.imageio.spi.IIOServiceProvider { *; }
|
||||
|
||||
# Skia - used by MacOSIconDecoder for bitmap conversion
|
||||
-keep class org.jetbrains.skia.** { *; }
|
||||
-dontnote org.jetbrains.skia.**
|
||||
|
||||
# Coil3 - image loading library, uses reflection for decoder registration
|
||||
-keep class coil3.** { *; }
|
||||
-dontnote coil3.**
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
/*
|
||||
* Copyright KG Soft
|
||||
*
|
||||
* 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 com.kgurgul.cpuinfo
|
||||
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import com.kgurgul.cpuinfo.di.initKoin
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.app_name
|
||||
import com.kgurgul.cpuinfo.shared.desktop_icon
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.skia.DirectContext
|
||||
|
||||
fun main() {
|
||||
@@ -13,14 +30,14 @@ fun main() {
|
||||
if (System.getProperty("os.name").equals("Linux", ignoreCase = true)) {
|
||||
try {
|
||||
DirectContext.makeGL().flush().close()
|
||||
} catch (e: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
System.setProperty("skiko.renderApi", "SOFTWARE")
|
||||
}
|
||||
}
|
||||
return application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "CPU Info",
|
||||
title = stringResource(Res.string.app_name),
|
||||
icon = painterResource(Res.drawable.desktop_icon),
|
||||
) {
|
||||
DesktopApp()
|
||||
|
||||
@@ -50,4 +50,11 @@
|
||||
<releases>
|
||||
<release date="2024-08-22" version="1.2.0" />
|
||||
</releases>
|
||||
<keywords>
|
||||
<keyword>cpu info</keyword>
|
||||
<keyword>hardware info</keyword>
|
||||
<keyword>system info</keyword>
|
||||
<keyword>device specs</keyword>
|
||||
<keyword>cpu details</keyword>
|
||||
</keywords>
|
||||
</component>
|
||||
@@ -0,0 +1,2 @@
|
||||
- Fix https://github.com/kamgurgul/cpu-info/issues/490
|
||||
- Add fastlane metadata
|
||||
@@ -0,0 +1,6 @@
|
||||
* Fix tile issue #490
|
||||
* Add more Wi-Fi info #92
|
||||
* Use Material You colors for progress #376
|
||||
* Add progress bar for RAM #448
|
||||
* Add more batter info #86
|
||||
* Libs update
|
||||
@@ -0,0 +1,16 @@
|
||||
CPU Info provide main information about hardware and software of your device:
|
||||
- CPU specification (with current frequency on specific cores)
|
||||
- GPU specification
|
||||
- RAM and storage state (internal, external and SD card)
|
||||
- Display metrics
|
||||
- Android OS details
|
||||
- Sensors data
|
||||
- Battery status
|
||||
- WiFi and Bluetooth mac address (on older Androids)
|
||||
- Audio card info
|
||||
- Native libraries used in another applications
|
||||
- CPU and battery temperature monitor
|
||||
- Installed applications
|
||||
- Running processes (on older Androids)
|
||||
|
||||
All issues and ideas can be posted on Github.
|
||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 182 KiB |