Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2db387892a | ||
|
|
57c24c4933 | ||
|
|
a60ec4d645 | ||
|
|
7849b450e8 | ||
|
|
bf679a9c4e | ||
|
|
b83f70afbe | ||
|
|
5e4cc29087 | ||
|
|
c185be0e5c | ||
|
|
817a5b102f | ||
|
|
2d3f7d8853 | ||
|
|
f2276cdde3 | ||
|
|
e5c9281c51 | ||
|
|
b4293c69b4 | ||
|
|
b064cb857f | ||
|
|
352dca246e | ||
|
|
42ea6516c7 | ||
|
|
06ce70131a | ||
|
|
32009bffd5 | ||
|
|
80400634e5 | ||
|
|
58b63e899a | ||
|
|
131b362fb4 | ||
|
|
c8c25e9d52 | ||
|
|
2fae678e2e | ||
|
|
06f97b68b7 | ||
|
|
a71cd1d183 | ||
|
|
508ccb8079 | ||
|
|
477683db4f | ||
|
|
11b16b8edd | ||
|
|
a5a326d0da | ||
|
|
19df5897f4 | ||
|
|
65918143ad | ||
|
|
bc22de91a1 | ||
|
|
a1284f9111 | ||
|
|
f88f2d732a | ||
|
|
e9f9922750 | ||
|
|
ce6c88a4db | ||
|
|
edb82bed8a | ||
|
|
4b4c3b2bfa | ||
|
|
2a57366029 | ||
|
|
510391593c | ||
|
|
a4aa57a6a4 | ||
|
|
dbcc84f65b | ||
|
|
8342fd2cbe | ||
|
|
0cf5eeaebd | ||
|
|
e7e7ac415c | ||
|
|
0bfd224bdb | ||
|
|
d830ba4a7d | ||
|
|
4969064f17 | ||
|
|
fefc23d2d9 | ||
|
|
1f70d680dc | ||
|
|
4d6bfe8008 | ||
|
|
67f910e278 | ||
|
|
ec897c9467 | ||
|
|
816161eda7 | ||
|
|
6a741a46b8 |
@@ -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.
|
||||
@@ -11,64 +11,106 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
run: ./gradlew allTests
|
||||
|
||||
- name: Generate code coverage
|
||||
run: ./gradlew androidApp:koverXmlReportDebug
|
||||
run: ./gradlew shared:koverXmlReportDebug
|
||||
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: app/build/coverage-report/result.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
build:
|
||||
needs: unit_tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
format: [
|
||||
{ type: "apk", command: "assembleRelease", output: "apk" },
|
||||
{ type: "aab", command: "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
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
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
|
||||
|
||||
- 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 > ./app/keystore.jks
|
||||
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
|
||||
with:
|
||||
name: app-release.${{ matrix.format.type }}
|
||||
path: app/build/outputs/${{ matrix.format.output }}/release/*.${{ matrix.format.type }}
|
||||
path: androidApp/build/outputs/${{ matrix.format.output }}/release/*.${{ matrix.format.type }}
|
||||
|
||||
- name: Upload mapping folder
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mapping-${{ matrix.format.type }}
|
||||
path: app/build/outputs/mapping/
|
||||
path: androidApp/build/outputs/mapping/
|
||||
|
||||
release:
|
||||
needs: build
|
||||
permissions:
|
||||
@@ -76,42 +118,50 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
with:
|
||||
name: app-release.apk
|
||||
|
||||
- name: Download APK mapping
|
||||
uses: actions/download-artifact@v4
|
||||
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
|
||||
with:
|
||||
name: app-release.aab
|
||||
|
||||
- name: Download AAB mapping
|
||||
uses: actions/download-artifact@v4
|
||||
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 }}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
name: Desktop release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
code_quality:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Unit tests
|
||||
run: ./gradlew test
|
||||
|
||||
build_msi:
|
||||
needs: code_quality
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Build MSI
|
||||
run: |
|
||||
./gradlew :desktopApp:packageReleaseMsi
|
||||
- name: Upload MSI
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CPU.Info-${{ github.event.inputs.version }}.msi
|
||||
path: desktopApp/build/compose/binaries/main-release/msi/*.msi
|
||||
|
||||
build_linux_x64_uber_jar:
|
||||
needs: code_quality
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Build Uber JAR
|
||||
run: ./gradlew :desktopApp:packageReleaseUberJarForCurrentOS
|
||||
- name: Upload Uber JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CPU-Info-linux-x64-${{ github.event.inputs.version }}-release.jar
|
||||
path: desktopApp/build/compose/jars/*.jar
|
||||
|
||||
build_mac_uber_jar:
|
||||
needs: code_quality
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Build Uber JAR
|
||||
run: ./gradlew :desktopApp:packageReleaseUberJarForCurrentOS
|
||||
- name: Upload Uber JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
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-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
key: ${{ runner.os }}-v1-${{ hashFiles('*.versions.toml') }}
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
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-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
|
||||
|
||||
- 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@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
path: ${{ env.RELEASE_PATH }}
|
||||
|
||||
build_mac_x64_dmg:
|
||||
needs: code_quality
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
key: ${{ runner.os }}-v1-${{ hashFiles('*.versions.toml') }}
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
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@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
path: ${{ env.RELEASE_PATH }}
|
||||
|
||||
tag_and_create_release:
|
||||
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
|
||||
- name: Download MSI
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: CPU.Info-${{ github.event.inputs.version }}.msi
|
||||
- name: Download Linux x64 Uber JAR
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: CPU-Info-linux-x64-${{ github.event.inputs.version }}-release.jar
|
||||
- name: Download Mac x64 DMG
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
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@v4
|
||||
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@v4
|
||||
with:
|
||||
name: CPU-Info-macos-arm64-${{ github.event.inputs.version }}-release.jar
|
||||
- name: Tag Release
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag jvm-${{ github.event.inputs.version }}
|
||||
git push origin jvm-${{ github.event.inputs.version }}
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: "*.msi, *.jar, *.dmg"
|
||||
draft: true
|
||||
name: "[JVM] ${{ github.event.inputs.version }}"
|
||||
tag: jvm-${{ github.event.inputs.version }}
|
||||
@@ -47,12 +47,12 @@ jobs:
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
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@v4
|
||||
with:
|
||||
bundle-id: ${{ secrets.IOS_BUNDLE_ID }}
|
||||
issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
|
||||
@@ -116,9 +116,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@v2
|
||||
with:
|
||||
app-path: ${{ env.ipa_path }}
|
||||
issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
|
||||
@@ -128,5 +128,5 @@ jobs:
|
||||
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 ios-${{ github.event.inputs.version }}
|
||||
git push origin ios-${{ github.event.inputs.version }}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Mac App Store release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
code_quality:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
build_and_release:
|
||||
needs: code_quality
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 18
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 18
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Cache KMP tooling
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.konan
|
||||
key: ${{ runner.os }}-v1-${{ hashFiles('*.versions.toml') }}
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Import certs
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MAC_CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.MAC_CERTIFICATES_PASSWORD }}
|
||||
|
||||
- name: Create Embedded Provision Profile
|
||||
run: |
|
||||
echo "$EMBEDDED_PROVISION" > desktopApp/embedded.provisionprofile.b64
|
||||
base64 -d -i desktopApp/embedded.provisionprofile.b64 > desktopApp/embedded.provisionprofile
|
||||
env:
|
||||
EMBEDDED_PROVISION: ${{ secrets.MAC_EMBEDDED_PROVISION }}
|
||||
|
||||
- name: Create Runtime Provision Profile
|
||||
run: |
|
||||
echo "$RUNTIME_PROVISION" > desktopApp/runtime.provisionprofile.b64
|
||||
base64 -d -i desktopApp/runtime.provisionprofile.b64 > desktopApp/runtime.provisionprofile
|
||||
env:
|
||||
RUNTIME_PROVISION: ${{ secrets.MAC_RUNTIME_PROVISION }}
|
||||
|
||||
- name: Create PKG
|
||||
run: ./gradlew packageReleasePkg -PmacOsAppStoreRelease=true
|
||||
|
||||
- uses: Apple-Actions/upload-testflight-build@v2
|
||||
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 }}
|
||||
@@ -18,11 +18,11 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Unit tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
run: ./gradlew allTests
|
||||
- name: Generate code coverage
|
||||
run: ./gradlew androidApp:koverXmlReportDebug
|
||||
run: ./gradlew shared:koverXmlReportDebug
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: app/build/coverage-report/result.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
@@ -0,0 +1,122 @@
|
||||
name: Wear OS release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
unit_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: corretto
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Unit tests
|
||||
run: ./gradlew allTests
|
||||
- name: Generate code coverage
|
||||
run: './gradlew shared:koverXmlReportDebug'
|
||||
- name: Upload test coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-coverage.xml
|
||||
path: shared/build/coverage-report/result.xml
|
||||
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@v4
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: corretto
|
||||
cache: gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- 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@v4
|
||||
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@v4
|
||||
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@v4
|
||||
- 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@v4
|
||||
with:
|
||||
name: app-release.apk
|
||||
- name: Download APK mapping
|
||||
uses: actions/download-artifact@v4
|
||||
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
|
||||
with:
|
||||
name: app-release.aab
|
||||
- name: Download AAB mapping
|
||||
uses: actions/download-artifact@v4
|
||||
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 }}'
|
||||
@@ -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"
|
||||
@@ -22,14 +22,23 @@ alt='Available on Amazon AppStore' height="80">](https://www.amazon.com/Kamil-Gu
|
||||
[<img src="info/app-store-badge.png"
|
||||
alt="Download on the App Store"
|
||||
height="80">](https://apps.apple.com/us/app/cpu-info/id6560116815)
|
||||
[<img src="info/flathub_badge.png"
|
||||
alt="Get it on Flathub"
|
||||
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
|
||||
| Android | Android TV | Wear OS | iOS | Desktop | Wasm |
|
||||
|:-------:|:----------:|:-------:|:---:|:-------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅<br/>[In preview](https://kgurgul.com/assets/cpuinfo/)<br/>Known issues: [1](https://youtrack.jetbrains.com/issue/CMP-6900/NavigationRail-items-not-visible-on-WASM-JS) |
|
||||
|
||||
# Tech stack
|
||||
## Tech stack
|
||||
|
||||
* [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html)
|
||||
* [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/)
|
||||
@@ -37,17 +46,15 @@ height="80">](https://apps.apple.com/us/app/cpu-info/id6560116815)
|
||||
* [Coroutines](https://github.com/Kotlin/kotlinx.coroutines)
|
||||
* [Koin](https://github.com/InsertKoinIO/koin)
|
||||
* [Coil](https://github.com/coil-kt/coil)
|
||||
* External C libraries - [pytorch/cpuinfo](https://github.com/pytorch/cpuinfo)
|
||||
* [DataStore](https://developer.android.com/kotlin/multiplatform/datastore)
|
||||
* [pytorch/cpuinfo](https://github.com/pytorch/cpuinfo)
|
||||
* [OSHI](https://github.com/oshi/oshi)
|
||||
|
||||
# TODO
|
||||
## TODO
|
||||
|
||||
* Shared unit and UI tests
|
||||
* Add more information about battery
|
||||
* Bring back apps size - use StorageStatsManager on Android if possible
|
||||
* Add translations framework
|
||||
* Upload Wear OS app to Play Store when SDK 35 will be supported
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
Copyright 2017 KG Soft
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
/build
|
||||
src/debug/google-services.json
|
||||
src/release/google-services.json
|
||||
@@ -6,25 +6,34 @@ plugins {
|
||||
|
||||
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 = 28
|
||||
targetSdk = 34
|
||||
minSdk = 24
|
||||
targetSdk = AndroidVersions.TARGET_SDK
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
targetProjectPath = ":androidApp"
|
||||
|
||||
testOptions.managedDevices.devices {
|
||||
create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel6Api33") {
|
||||
device = "Pixel 6"
|
||||
apiLevel = 33
|
||||
systemImageSource = "aosp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
useConnectedDevices = true
|
||||
managedDevices += "pixel6Api33"
|
||||
useConnectedDevices = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -38,7 +47,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!! },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@ class BaselineProfileGenerator {
|
||||
waitForAsyncContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class StartupBenchmarks {
|
||||
measureBlock = {
|
||||
startActivityAndWait()
|
||||
waitForAsyncContent()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.baselineprofile)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.licenses)
|
||||
alias(libs.plugins.serialization)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
@@ -20,15 +25,15 @@ kotlin {
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -38,18 +43,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,66 +92,35 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
saveInSrc = true
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
reportsDestination = layout.buildDirectory.dir("compose_compiler")
|
||||
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(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)
|
||||
androidTestImplementation(libs.androidx.test.espresso.core)
|
||||
androidTestImplementation(libs.androidx.test.espresso.contrib)
|
||||
androidTestImplementation(libs.compose.ui.test)
|
||||
|
||||
debugImplementation(libs.compose.ui.testManifest)
|
||||
|
||||
androidTestUtil(libs.androidx.test.orchestrator)
|
||||
|
||||
"baselineProfile"(project(":androidApp:baselineprofile"))
|
||||
}
|
||||
|
||||
kover {
|
||||
reports {
|
||||
variant("debug") {
|
||||
filters {
|
||||
includes {
|
||||
packages(KoverConfig.includedPackages)
|
||||
}
|
||||
excludes {
|
||||
packages(KoverConfig.excludedPackages)
|
||||
classes(KoverConfig.excludedClasses)
|
||||
annotatedBy(KoverConfig.excludedAnnotations)
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
htmlDir = layout.buildDirectory.dir("coverage-report/html")
|
||||
}
|
||||
|
||||
xml {
|
||||
xmlFile = layout.buildDirectory.file("coverage-report/result.xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,20 @@
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<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:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -35,6 +46,30 @@
|
||||
<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
|
||||
@@ -52,4 +87,4 @@
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -18,12 +18,10 @@ 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() {
|
||||
|
||||
@@ -33,10 +31,9 @@ class CpuInfoApp : Application() {
|
||||
androidContext(this@CpuInfoApp)
|
||||
modules(
|
||||
androidModule,
|
||||
AppInitializersModule().module,
|
||||
sharedModule,
|
||||
)
|
||||
}
|
||||
AppInitializerComponent().init(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
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.appcompat.app.AppCompatActivity
|
||||
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
|
||||
@@ -18,16 +23,21 @@ 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 : AppCompatActivity() {
|
||||
class HostActivity : ComponentActivity() {
|
||||
|
||||
private val viewModel: HostViewModel by viewModel()
|
||||
private val androidShortcutManager: AndroidShortcutManager by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val splashScreen = installSplashScreen()
|
||||
@@ -55,8 +65,33 @@ class HostActivity : AppCompatActivity() {
|
||||
)
|
||||
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.primaryContainer,
|
||||
onSecondary = dynamicDarkColors.onPrimaryContainer,
|
||||
)
|
||||
}
|
||||
|
||||
dynamicColor && !darkTheme -> {
|
||||
val dynamicLightColors = dynamicLightColorScheme(LocalContext.current)
|
||||
LightColors.copy(
|
||||
secondary = dynamicLightColors.secondary,
|
||||
onSecondary = dynamicLightColors.onSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColors
|
||||
else -> LightColors
|
||||
}
|
||||
LaunchedEffect(uiState.isApplicationSectionVisible) {
|
||||
androidShortcutManager.createShortcuts(uiState.isApplicationSectionVisible)
|
||||
}
|
||||
CpuInfoTheme(
|
||||
useDarkTheme = darkTheme,
|
||||
colors = colors,
|
||||
) {
|
||||
HostScreen(
|
||||
viewModel = viewModel,
|
||||
|
||||
@@ -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
|
||||
@@ -7,25 +7,28 @@ import androidx.annotation.RequiresApi
|
||||
import com.kgurgul.cpuinfo.R
|
||||
import com.kgurgul.cpuinfo.data.provider.CpuDataProvider
|
||||
import com.kgurgul.cpuinfo.utils.IDispatchersProvider
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -45,7 +48,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,7 +58,7 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
refreshingJob?.cancel()
|
||||
refreshingJob = launch {
|
||||
while (true) {
|
||||
val load = withContext(dispatchersProvider.io) { averageCPUFreq() }
|
||||
val load = getAverageCPUFreq()
|
||||
qsTile.label = "Avg ${load}MHz"
|
||||
qsTile.icon = getLoadIcon(load)
|
||||
qsTile.updateTile()
|
||||
@@ -70,7 +73,7 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
job.cancel()
|
||||
coroutineContext.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -79,28 +82,32 @@ class CpuTileService : TileService(), CoroutineScope, KoinComponent {
|
||||
val freqThirds = freqDiff / 3
|
||||
val loadEnum = when {
|
||||
avgLoad >= minMaxAvg.second - freqThirds -> CPULoad.High
|
||||
minMaxAvg.second - freqThirds > avgLoad && avgLoad >= minMaxAvg.first + freqThirds -> CPULoad.Medium
|
||||
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.getNumberOfCores()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
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
|
||||
import com.kgurgul.cpuinfo.ui.theme.lightPrimary
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class TvActivity : ComponentActivity() {
|
||||
|
||||
private val viewModel: HostViewModel by viewModel()
|
||||
|
||||
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 {}
|
||||
}
|
||||
CpuInfoTheme(
|
||||
useDarkTheme = darkTheme,
|
||||
) {
|
||||
TvHostScreen(
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
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,513 @@
|
||||
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,186 @@
|
||||
@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,190 @@
|
||||
package com.kgurgul.cpuinfo.tv.features.information.cpu
|
||||
|
||||
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.compose.ui.platform.testTag
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.kgurgul.cpuinfo.domain.model.CpuData
|
||||
import com.kgurgul.cpuinfo.features.information.cpu.CpuInfoViewModel
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.cpu_abi
|
||||
import com.kgurgul.cpuinfo.shared.cpu_cores
|
||||
import com.kgurgul.cpuinfo.shared.cpu_current_frequency
|
||||
import com.kgurgul.cpuinfo.shared.cpu_frequency
|
||||
import com.kgurgul.cpuinfo.shared.cpu_frequency_stopped
|
||||
import com.kgurgul.cpuinfo.shared.cpu_has_neon
|
||||
import com.kgurgul.cpuinfo.shared.cpu_l1d
|
||||
import com.kgurgul.cpuinfo.shared.cpu_l1i
|
||||
import com.kgurgul.cpuinfo.shared.cpu_l2
|
||||
import com.kgurgul.cpuinfo.shared.cpu_l3
|
||||
import com.kgurgul.cpuinfo.shared.cpu_l4
|
||||
import com.kgurgul.cpuinfo.shared.cpu_soc_name
|
||||
import com.kgurgul.cpuinfo.shared.no
|
||||
import com.kgurgul.cpuinfo.shared.yes
|
||||
import com.kgurgul.cpuinfo.tv.ui.components.TvListItem
|
||||
import com.kgurgul.cpuinfo.ui.components.CpuProgressBar
|
||||
import com.kgurgul.cpuinfo.ui.components.ItemValueRow
|
||||
import com.kgurgul.cpuinfo.ui.theme.spacingSmall
|
||||
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),
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item(key = "__soc_name") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_soc_name),
|
||||
value = cpuData.processorName,
|
||||
modifier = Modifier
|
||||
.testTag(TvCpuInfoScreenTestTags.SOCKET_NAME),
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "__abi") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_abi),
|
||||
value = cpuData.abi,
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "__cores") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_cores),
|
||||
value = cpuData.coreNumber.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
item(key = "__has_neon") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_has_neon),
|
||||
value = if (cpuData.hasArmNeon) {
|
||||
stringResource(Res.string.yes)
|
||||
} else {
|
||||
stringResource(Res.string.no)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (cpuData.l1dCaches.isNotEmpty()) {
|
||||
item(key = "__l1d") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_l1d),
|
||||
value = cpuData.l1dCaches,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpuData.l1iCaches.isNotEmpty()) {
|
||||
item(key = "__l1i") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_l1i),
|
||||
value = cpuData.l1iCaches,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpuData.l2Caches.isNotEmpty()) {
|
||||
item(key = "__l2") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_l2),
|
||||
value = cpuData.l2Caches,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpuData.l3Caches.isNotEmpty()) {
|
||||
item(key = "__l3") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_l3),
|
||||
value = cpuData.l3Caches,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cpuData.l4Caches.isNotEmpty()) {
|
||||
item(key = "__l4") {
|
||||
TvListItem {
|
||||
ItemValueRow(
|
||||
title = stringResource(Res.string.cpu_l4),
|
||||
value = cpuData.l4Caches,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FrequencyItem(index: Int, frequency: CpuData.Frequency) {
|
||||
val currentFreq = if (frequency.current != -1L) {
|
||||
stringResource(
|
||||
Res.string.cpu_current_frequency,
|
||||
index,
|
||||
frequency.current.toString(),
|
||||
)
|
||||
} else {
|
||||
stringResource(Res.string.cpu_frequency_stopped, index)
|
||||
}
|
||||
val minFreq = if (frequency.min != -1L) {
|
||||
stringResource(Res.string.cpu_frequency, "0")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val maxFreq = if (frequency.max != -1L) {
|
||||
stringResource(Res.string.cpu_frequency, frequency.max.toString())
|
||||
} 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,63 @@
|
||||
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,54 @@
|
||||
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,54 @@
|
||||
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,73 @@
|
||||
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,54 @@
|
||||
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,80 @@
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.kgurgul.cpuinfo.features.information.storage
|
||||
package com.kgurgul.cpuinfo.tv.features.information.storage
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -11,55 +10,66 @@ 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 org.koin.compose.viewmodel.koinViewModel
|
||||
import kotlin.math.roundToInt
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun StorageScreen(
|
||||
fun TvStorageInfoScreen(
|
||||
viewModel: StorageInfoViewModel = koinViewModel(),
|
||||
) {
|
||||
registerStorageMountingListener(viewModel::onRefreshStorage)
|
||||
val uiState by viewModel.uiStateFlow.collectAsStateWithLifecycle()
|
||||
StorageScreen(
|
||||
uiState = uiState
|
||||
TvStorageInfoScreen(
|
||||
uiState = uiState,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StorageScreen(
|
||||
fun TvStorageInfoScreen(
|
||||
uiState: StorageInfoViewModel.UiState,
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(spacingSmall),
|
||||
verticalArrangement = Arrangement.spacedBy(spacingSmall),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
items(
|
||||
uiState.storageItems,
|
||||
key = { it.id }
|
||||
key = { it.id },
|
||||
) { storageItem ->
|
||||
val label = storageItem.label
|
||||
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 = storageItem.storageUsed.toFloat() / storageItem.storageTotal.toFloat()
|
||||
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)
|
||||
CpuProgressBar(
|
||||
label = storageDesc,
|
||||
progress = progress,
|
||||
progressHeight = 32.dp,
|
||||
prefixImageRes = storageItem.iconDrawable,
|
||||
minMaxValues = minMaxValues,
|
||||
modifier = Modifier.focusable(),
|
||||
)
|
||||
Utils.humanReadableByteCount(storageItem.storageTotal)
|
||||
TvListItem {
|
||||
CpuProgressBar(
|
||||
label = storageDesc,
|
||||
progress = progress,
|
||||
progressHeight = 32.dp,
|
||||
prefixImageRes = storageItem.iconDrawable,
|
||||
minMaxValues = minMaxValues,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun registerStorageMountingListener(onRefresh: () -> Unit)
|
||||
@@ -0,0 +1,302 @@
|
||||
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,196 @@
|
||||
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.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(
|
||||
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(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(spacingMedium)
|
||||
.then(modifier),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.no_temp_data),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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,128 @@
|
||||
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,27 @@
|
||||
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.3f),
|
||||
),
|
||||
headlineContent: @Composable () -> Unit,
|
||||
) {
|
||||
DenseListItem(
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
scale = ListItemDefaults.scale(focusedScale = 1.01f),
|
||||
colors = colors,
|
||||
headlineContent = headlineContent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -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(),
|
||||
)
|
||||
}*/
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -8,4 +8,4 @@ abstract class BaseTestSuit {
|
||||
|
||||
@get:Rule
|
||||
val androidComposeRule = createAndroidComposeRule<HostActivity>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,5 @@ class InfoContainerPage(
|
||||
) {
|
||||
|
||||
fun assertViewDisplayed() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,4 @@ class InfoTestSuit : BaseTestSuit() {
|
||||
@Test
|
||||
fun checkInfoTabs() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,35 +7,19 @@ plugins {
|
||||
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
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.withType<KotlinCompile>().all {
|
||||
compilerOptions {
|
||||
//allWarningsAsErrors = true
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
|
||||
if (project.findProperty("composeCompilerReports") == "true") {
|
||||
freeCompilerArgs.addAll(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
project.layout.buildDirectory.dir("compose_compiler").get()
|
||||
.asFile.absolutePath
|
||||
)
|
||||
}
|
||||
if (project.findProperty("composeCompilerMetrics") == "true") {
|
||||
freeCompilerArgs.addAll(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
project.layout.buildDirectory.dir("compose-metrics").get()
|
||||
.asFile.absolutePath
|
||||
)
|
||||
}
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
object SigningConfig {
|
||||
object AndroidSigningConfig {
|
||||
|
||||
const val KEY_PATH = "KEYSTORE_PATH"
|
||||
const val KEY_PASS = "KEYSTORE_PASS"
|
||||
@@ -22,15 +22,15 @@ object SigningConfig {
|
||||
} 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()
|
||||
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()
|
||||
} 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)
|
||||
@@ -42,4 +42,4 @@ object SigningConfig {
|
||||
getDebugProperties(rootDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
object AndroidVersions {
|
||||
|
||||
const val MIN_SDK = 21
|
||||
const val WEAR_MIN_SDK = 26
|
||||
const val TARGET_SDK = 35
|
||||
const val COMPILE_SDK = 35
|
||||
|
||||
private const val VERSION_MAJOR = 6
|
||||
private const val VERSION_MINOR = 4
|
||||
private const val VERSION_PATCH = 0
|
||||
|
||||
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 = "27.1.12297006"
|
||||
}
|
||||
@@ -4,7 +4,8 @@ enum class ReleaseType(private val level: Int) {
|
||||
ALPHA(10),
|
||||
BETA(20),
|
||||
RC(60),
|
||||
RELEASE(100);
|
||||
RELEASE(100),
|
||||
;
|
||||
|
||||
fun isEqualOrMoreStableThan(other: ReleaseType): Boolean = level >= other.level
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ object KoverConfig {
|
||||
"*_Factory*",
|
||||
)
|
||||
val excludedAnnotations = "*Generated*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -21,27 +21,52 @@ compose.desktop {
|
||||
mainClass = "com.kgurgul.cpuinfo.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Pkg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
|
||||
packageName = "CPU-Info"
|
||||
packageVersion = "1.0.0"
|
||||
packageVersion = "1.6.0"
|
||||
|
||||
modules("jdk.unsupported")
|
||||
modules("jdk.unsupported.desktop")
|
||||
|
||||
val iconsRoot = project.file("desktop-icons")
|
||||
macOS {
|
||||
iconFile.set(iconsRoot.resolve("icon-mac.icns"))
|
||||
}
|
||||
windows {
|
||||
iconFile.set(iconsRoot.resolve("icon-windows.ico"))
|
||||
menuGroup = "CPU-Info"
|
||||
upgradeUuid = "d4a688eb-758f-44e8-81bb-8367880c5c95"
|
||||
}
|
||||
linux {
|
||||
iconFile.set(iconsRoot.resolve("icon-linux.png"))
|
||||
}
|
||||
macOS {
|
||||
bundleID = "com.kgurgul.cpuinfo"
|
||||
iconFile.set(iconsRoot.resolve("icon-mac.icns"))
|
||||
minimumSystemVersion = "12.0"
|
||||
|
||||
val isAppStoreRelease = project.property("macOsAppStoreRelease")
|
||||
.toString()
|
||||
.toBoolean()
|
||||
val withNotarization = project.property("macOsNotarization")
|
||||
.toString()
|
||||
.toBoolean()
|
||||
appStore = isAppStoreRelease
|
||||
if (isAppStoreRelease) {
|
||||
provisioningProfile.set(project.file("embedded.provisionprofile"))
|
||||
runtimeProvisioningProfile.set(project.file("runtime.provisionprofile"))
|
||||
entitlementsFile.set(project.file("entitlements.plist"))
|
||||
runtimeEntitlementsFile.set(project.file("runtime-entitlements.plist"))
|
||||
}
|
||||
signing {
|
||||
sign.set(isAppStoreRelease || withNotarization)
|
||||
identity.set("Kamil Gurgul")
|
||||
}
|
||||
}
|
||||
windows {
|
||||
iconFile.set(iconsRoot.resolve("icon-windows.ico"))
|
||||
menuGroup = "CPU-Info"
|
||||
upgradeUuid = "d4a688eb-758f-44e8-81bb-8367880c5c95"
|
||||
}
|
||||
linux {
|
||||
iconFile.set(iconsRoot.resolve("icon-linux.png"))
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes.release.proguard {
|
||||
isEnabled = false
|
||||
version = "7.4.2"
|
||||
configurationFiles.from("rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>B6LJ2PS259.com.kgurgul.cpuinfo</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>B6LJ2PS259</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -3,4 +3,27 @@
|
||||
-dontwarn org.slf4j.**
|
||||
|
||||
# Obfuscation breaks coroutines/ktor for some reason
|
||||
-dontobfuscate
|
||||
-dontobfuscate
|
||||
|
||||
# DataStore
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
-dontwarn androidx.datastore.preferences.**
|
||||
|
||||
# Compose
|
||||
-optimizations !class/enum/unboxing
|
||||
-keep,includecode,allowobfuscation,allowshrinking class androidx.compose.runtime.SnapshotStateKt** { *; }
|
||||
-dontwarn androidx.compose.runtime.**
|
||||
|
||||
# Serialization
|
||||
-keep @kotlinx.serialization.Serializable class * { *; }
|
||||
|
||||
# JNA
|
||||
-keep class com.sun.jna.** { *; }
|
||||
|
||||
# OSHI
|
||||
-keep class oshi.** { *; }
|
||||
|
||||
# Okio
|
||||
-keep class okio.** { *; }
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.temporary-exception.iokit-user-client-class</key>
|
||||
<array>
|
||||
<string>AppleSMCClient</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -4,15 +4,25 @@ 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() {
|
||||
initKoin()
|
||||
if (System.getProperty("os.name").equals("Linux", ignoreCase = true)) {
|
||||
try {
|
||||
DirectContext.makeGL().flush().close()
|
||||
} catch (e: 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()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Exec=cpuinfo
|
||||
Icon=com.kgurgul.cpuinfo
|
||||
Name=CPU Info
|
||||
Type=Application
|
||||
Categories=Utility
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.kgurgul.cpuinfo</id>
|
||||
<name>CPU Info</name>
|
||||
<summary>Information about device hardware and software</summary>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>Apache-2.0</project_license>
|
||||
<description>
|
||||
<p>CPU Info provide main information about hardware and software of your device. Because of
|
||||
the flatpak nature not all features will be available. Currently supported:
|
||||
</p>
|
||||
<ul>
|
||||
<li>CPU specification</li>
|
||||
<li>GPU specification</li>
|
||||
<li>RAM and storage state</li>
|
||||
<li>Displays ids</li>
|
||||
<li>OS details</li>
|
||||
<li>Sensors data</li>
|
||||
<li>Hardware info</li>
|
||||
<li>CPU temperature</li>
|
||||
</ul>
|
||||
</description>
|
||||
<developer id="com.kgurgul">
|
||||
<name>Kamil Gurgul</name>
|
||||
</developer>
|
||||
<url type="homepage">https://github.com/kamgurgul/cpu-info</url>
|
||||
<url type="bugtracker">https://github.com/kamgurgul/cpu-info/issues</url>
|
||||
<launchable type="desktop-id">com.kgurgul.cpuinfo.desktop</launchable>
|
||||
<content_rating type="oars-1.1" />
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>
|
||||
https://github.com/kamgurgul/cpu-info/blob/jvm-1.2.0/info/screenshots/Linux_1.png?raw=true
|
||||
</image>
|
||||
<caption>CPU specification</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>
|
||||
https://github.com/kamgurgul/cpu-info/blob/jvm-1.2.0/info/screenshots/Linux_2.png?raw=true
|
||||
</image>
|
||||
<caption>Storage info</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>
|
||||
https://github.com/kamgurgul/cpu-info/blob/jvm-1.2.0/info/screenshots/Linux_3.png?raw=true
|
||||
</image>
|
||||
<caption>Temperature info</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release date="2024-08-22" version="1.2.0" />
|
||||
</releases>
|
||||
</component>
|
||||
@@ -1,15 +1,16 @@
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx8192M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx8192M"
|
||||
systemProp.javax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
|
||||
systemProp.javax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
|
||||
systemProp.javax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
|
||||
#Android
|
||||
org.gradle.jvmargs=-Xmx8192M -Dfile.encoding=UTF-8
|
||||
# Android
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
#Kotlin Multiplatform
|
||||
# Kotlin Multiplatform
|
||||
kotlin.apple.xcodeCompatibility.nowarn=true
|
||||
kotlin.code.style=official
|
||||
kotlin.daemon.jvmargs=-Xmx8192M
|
||||
kotlin.native.ignoreDisabledTargets=true
|
||||
kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
|
||||
kotlin.mpp.enableCInteropCommonization=true
|
||||
# Custom
|
||||
macOsAppStoreRelease=false
|
||||
macOsNotarization=false
|
||||
|
||||
@@ -1,47 +1,56 @@
|
||||
[versions]
|
||||
androidGradlePlugin = "8.5.2"
|
||||
androidxActivity = "1.9.1"
|
||||
androidGradlePlugin = "8.9.2"
|
||||
androidxActivity = "1.10.1"
|
||||
androidxArchCoreTesting = "2.2.0"
|
||||
androidxCore = "1.16.0"
|
||||
androidxCoreSpalshscreen = "1.0.1"
|
||||
androidxDatastorePreferences = "1.1.1"
|
||||
androidxDatastorePreferences = "1.1.4"
|
||||
androidxEspresso = "3.6.1"
|
||||
androidxLifecycle = "2.8.0"
|
||||
androidxNavigation = "2.8.0-alpha08"
|
||||
androidxOrchestrator = "1.5.0"
|
||||
androidxLifecycle = "2.8.4"
|
||||
androidxNavigation = "2.8.0-alpha12"
|
||||
androidxNavigationAndroid = "2.8.9"
|
||||
androidxOrchestrator = "1.5.1"
|
||||
androidxTestCore = "1.6.1"
|
||||
androidxTestExt = "1.2.1"
|
||||
androidxTestRules = "1.6.1"
|
||||
androidxTestRunner = "1.6.1"
|
||||
baselineprofile = "1.2.4"
|
||||
benchmarkMacroJunit4 = "1.2.4"
|
||||
coil = "3.0.0-alpha10"
|
||||
composeUiTest = "1.6.8"
|
||||
composeMultiplatform = "1.7.0-alpha02"
|
||||
dependencyUpdate = "0.51.0"
|
||||
kermit = "2.0.4"
|
||||
koin = "4.0.0-RC1"
|
||||
koinAnnotations = "1.3.1"
|
||||
kotlin = "2.0.10"
|
||||
kotlinxCoroutines = "1.9.0-RC"
|
||||
kotlinxImmutable = "0.3.7"
|
||||
kover = "0.8.3"
|
||||
ksp = "2.0.10-1.0.24"
|
||||
mockitoKotlin = "5.4.0"
|
||||
profileinstaller = "1.3.1"
|
||||
androidxTestRunner = "1.6.2"
|
||||
androidxTv = "1.0.0"
|
||||
androidxWear = "1.4.1"
|
||||
baselineprofile = "1.3.4"
|
||||
benchmarkMacroJunit4 = "1.3.4"
|
||||
coil = "3.1.0"
|
||||
composeAdaptive = "1.0.1"
|
||||
composeUiTest = "1.8.0"
|
||||
composeMultiplatform = "1.7.3"
|
||||
dependencyUpdate = "0.52.0"
|
||||
horologist = "0.7.12-alpha"
|
||||
kermit = "2.0.5"
|
||||
koin = "4.0.4"
|
||||
kotlin = "2.1.20"
|
||||
kotlinxBrowser = "0.3"
|
||||
kotlinxCoroutines = "1.10.2"
|
||||
kotlinxImmutable = "0.3.8"
|
||||
kotlinxSerialization = "1.8.1"
|
||||
kover = "0.9.1"
|
||||
ksp = "2.1.20-2.0.0"
|
||||
licenses = "2.9"
|
||||
oshi = "6.8.1"
|
||||
profileinstaller = "1.4.1"
|
||||
relinker = "1.4.5"
|
||||
turbine = "1.1.0"
|
||||
turbine = "1.2.0"
|
||||
uiautomator = "2.3.0"
|
||||
windowSize = "0.5.0"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
|
||||
androidx-arch-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "androidxArchCoreTesting" }
|
||||
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
||||
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSpalshscreen" }
|
||||
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidxDatastorePreferences" }
|
||||
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
||||
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidxNavigation" }
|
||||
androidx-navigation-compose-android = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigationAndroid" }
|
||||
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
|
||||
androidx-test-core = { group = "androidx.test", name = "core-ktx", version.ref = "androidxTestCore" }
|
||||
androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" }
|
||||
@@ -51,22 +60,28 @@ androidx-test-orchestrator = { group = "androidx.test", name = "orchestrator", v
|
||||
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
|
||||
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" }
|
||||
androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
|
||||
androidx-tv = { module = "androidx.tv:tv-material", version.ref = "androidxTv" }
|
||||
androidx-wear-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "androidxWear" }
|
||||
androidx-wear-material = { module = "androidx.wear.compose:compose-material", version.ref = "androidxWear" }
|
||||
androidx-wear-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "androidxWear" }
|
||||
coil = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
|
||||
compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeUiTest" }
|
||||
compose-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive", version.ref = "composeAdaptive" }
|
||||
compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4-android", version.ref = "composeUiTest" }
|
||||
compose-ui-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "composeUiTest" }
|
||||
horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" }
|
||||
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" }
|
||||
kermit-kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
||||
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
|
||||
koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koinAnnotations" }
|
||||
koin-compose-viewodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
koin-kspCompiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koinAnnotations" }
|
||||
koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
|
||||
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinxBrowser" }
|
||||
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
|
||||
material3-windowSize = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version.ref = "windowSize" }
|
||||
mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" }
|
||||
oshi = { group = "com.github.oshi", name = "oshi-core", version = "6.6.2" }
|
||||
kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
oshi = { group = "com.github.oshi", name = "oshi-core", version.ref = "oshi" }
|
||||
relinker = { group = "com.getkeepsafe.relinker", name = "relinker", version.ref = "relinker" }
|
||||
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
|
||||
|
||||
@@ -81,3 +96,5 @@ dependencyUpdate = { id = "com.github.ben-manes.versions", version.ref = "depend
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
licenses = { id = "com.github.jk1.dependency-license-report", version.ref = "licenses" }
|
||||
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -203,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -12,20 +12,20 @@
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
||||
9805A21C2C43EF9F009D4819 /* IosSoftwareDataProviderImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9805A21B2C43EF9F009D4819 /* IosSoftwareDataProviderImpl.swift */; };
|
||||
9843E8962C2C12230005FFCC /* libcpuinfo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9843E8952C2C12230005FFCC /* libcpuinfo.xcframework */; };
|
||||
9843E8972C2C12230005FFCC /* libcpuinfo.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9843E8952C2C12230005FFCC /* libcpuinfo.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9878BEA22CF8E8A3007810F2 /* libcpuinfo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9843E8952C2C12230005FFCC /* libcpuinfo.xcframework */; };
|
||||
9878BEA32CF8E8A3007810F2 /* libcpuinfo.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9843E8952C2C12230005FFCC /* libcpuinfo.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
98A3D5782C2FF1A700C60DE5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 98A3D5772C2FF1A700C60DE5 /* LaunchScreen.storyboard */; };
|
||||
98E07D812C3CFD7F00B6C8C3 /* IosHardwareDataProviderImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98367C552C33D3EC00C071E8 /* IosHardwareDataProviderImpl.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
98356FC52C2AC2FC0077BDD7 /* Embed Frameworks */ = {
|
||||
9878BEA42CF8E8A3007810F2 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
9843E8972C2C12230005FFCC /* libcpuinfo.xcframework in Embed Frameworks */,
|
||||
9878BEA32CF8E8A3007810F2 /* libcpuinfo.xcframework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -51,7 +51,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9843E8962C2C12230005FFCC /* libcpuinfo.xcframework in Frameworks */,
|
||||
9878BEA22CF8E8A3007810F2 /* libcpuinfo.xcframework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -126,7 +126,7 @@
|
||||
7555FF77242A565900829871 /* Sources */,
|
||||
B92378962B6B1156000C7307 /* Frameworks */,
|
||||
7555FF79242A565900829871 /* Resources */,
|
||||
98356FC52C2AC2FC0077BDD7 /* Embed Frameworks */,
|
||||
9878BEA42CF8E8A3007810F2 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -156,7 +156,6 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@@ -166,6 +165,7 @@
|
||||
mainGroup = 7555FF72242A565900829871;
|
||||
packageReferences = (
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 7555FF7C242A565900829871 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
||||
@@ -9,7 +9,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
@@ -20,3 +20,5 @@ include(":androidApp")
|
||||
include(":androidApp:baselineprofile")
|
||||
include(":desktopApp")
|
||||
include(":shared")
|
||||
include(":wearOsApp")
|
||||
include(":webApp")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import org.jetbrains.compose.ExperimentalComposeLibrary
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
@@ -8,39 +9,42 @@ plugins {
|
||||
alias(libs.plugins.compose.multiplatform)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.licenses)
|
||||
alias(libs.plugins.serialization)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
version = "1.0"
|
||||
|
||||
kotlin {
|
||||
androidTarget()
|
||||
androidTarget {
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
|
||||
}
|
||||
|
||||
val xcf = XCFramework()
|
||||
val iosTargets = listOf(iosX64(), iosArm64(), iosSimulatorArm64())
|
||||
val iosTargets = listOf(iosX64(), iosArm64())
|
||||
iosTargets.forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
baseName = "shared"
|
||||
isStatic = true
|
||||
binaryOption("bundleId", "com.kgurgul.cpuinfo.shared")
|
||||
xcf.add(this)
|
||||
}
|
||||
|
||||
val baseCinteropPath = "$projectDir/src/nativeInterop/cinterop/"
|
||||
val libcpuinfoPath = when (iosTarget.name) {
|
||||
"iosX65" -> "${baseCinteropPath}libcpuinfo/libcpuinfo.xcframework/ios-x86_64-simulator/"
|
||||
"iosSimulatorArm64" -> "${baseCinteropPath}libcpuinfo/libcpuinfo.xcframework/ios-arm64-simulator/"
|
||||
"iosX64" -> "${baseCinteropPath}libcpuinfo/libcpuinfo.xcframework/ios-x86_64-simulator/"
|
||||
else -> "${baseCinteropPath}libcpuinfo/libcpuinfo.xcframework/ios-arm64/"
|
||||
}
|
||||
iosTarget.compilations.getByName("main") {
|
||||
val libcpuinfo by cinterops.creating {
|
||||
definitionFile.set(
|
||||
project.file("src/nativeInterop/cinterop/libcpuinfo/libcpuinfo.def")
|
||||
project.file("src/nativeInterop/cinterop/libcpuinfo/libcpuinfo.def"),
|
||||
)
|
||||
compilerOpts(
|
||||
"-framework",
|
||||
"libcpuinfo",
|
||||
"-F$libcpuinfoPath"
|
||||
"-F$libcpuinfoPath",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -55,56 +59,100 @@ kotlin {
|
||||
|
||||
jvm("desktop")
|
||||
|
||||
applyDefaultHierarchyTemplate()
|
||||
@OptIn(ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
applyDefaultHierarchyTemplate {
|
||||
common {
|
||||
group("mobile") {
|
||||
withIos()
|
||||
withAndroidTarget()
|
||||
withJvm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings {
|
||||
optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
|
||||
optIn("androidx.compose.material3.ExperimentalMaterial3Api")
|
||||
optIn("androidx.compose.material.ExperimentalMaterialApi")
|
||||
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
|
||||
optIn("org.koin.core.annotation.KoinExperimentalAPI")
|
||||
optIn("kotlinx.cinterop.ExperimentalForeignApi")
|
||||
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||
}
|
||||
}
|
||||
|
||||
commonMain {
|
||||
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
|
||||
dependencies {
|
||||
api(compose.components.resources)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
api(compose.foundation)
|
||||
api(compose.material3)
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.ui)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.coil)
|
||||
api(libs.androidx.navigation.compose)
|
||||
api(libs.coil)
|
||||
implementation(libs.compose.adaptive)
|
||||
implementation(libs.kermit.kermit)
|
||||
api(libs.koin.annotations)
|
||||
implementation(libs.koin.compose.viewodel)
|
||||
api(libs.koin.compose.viewodel)
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
api(libs.kotlinx.immutable)
|
||||
implementation(libs.material3.windowSize)
|
||||
api(libs.kotlinx.serialization)
|
||||
}
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(compose.preview)
|
||||
implementation(compose.uiTooling)
|
||||
implementation(libs.koin.android)
|
||||
val mobileMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
}
|
||||
}
|
||||
|
||||
val desktopMain by getting
|
||||
desktopMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.oshi)
|
||||
androidMain {
|
||||
dependencies {
|
||||
implementation(compose.preview)
|
||||
api(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.navigation.compose.android)
|
||||
api(libs.androidx.core.splashscreen)
|
||||
api(libs.androidx.tv)
|
||||
api(libs.koin.android)
|
||||
implementation(libs.relinker)
|
||||
}
|
||||
}
|
||||
|
||||
iosMain {
|
||||
dependencies {
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
}
|
||||
}
|
||||
|
||||
val desktopMain by getting {
|
||||
dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.oshi)
|
||||
}
|
||||
}
|
||||
|
||||
val wasmJsMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.browser)
|
||||
implementation(npm("cross-spawn", "7.0.5"))
|
||||
implementation(npm("path-to-regexp", "0.1.12"))
|
||||
}
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
@@ -112,6 +160,9 @@ kotlin {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.turbine)
|
||||
|
||||
@OptIn(ExperimentalComposeLibrary::class)
|
||||
implementation(compose.uiTest)
|
||||
}
|
||||
@@ -122,9 +173,8 @@ kotlin {
|
||||
implementation(kotlin("test-junit"))
|
||||
implementation(libs.androidx.test.core)
|
||||
implementation(libs.androidx.arch.core.testing)
|
||||
implementation(libs.koin.test)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.mockito.kotlin)
|
||||
implementation(libs.turbine)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,38 +188,52 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation(libs.compose.ui.test)
|
||||
|
||||
debugImplementation(compose.uiTooling)
|
||||
debugImplementation(libs.compose.ui.testManifest)
|
||||
}
|
||||
|
||||
compose.resources {
|
||||
publicResClass = true
|
||||
packageOfResClass = "com.kgurgul.cpuinfo.shared"
|
||||
generateResClass = always
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
reportsDestination = layout.buildDirectory.dir("compose_compiler")
|
||||
metricsDestination = layout.buildDirectory.dir("compose_compiler")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = Versions.COMPILE_SDK
|
||||
compileSdk = AndroidVersions.COMPILE_SDK
|
||||
namespace = "com.kgurgul.cpuinfo.shared"
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
sourceSets["main"].res.srcDirs("src/androidMain/res")
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Versions.MIN_SDK
|
||||
minSdk = AndroidVersions.MIN_SDK
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments += "-DANDROID_STL=c++_static"
|
||||
arguments += listOf(
|
||||
"-DANDROID_STL=c++_static",
|
||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON",
|
||||
)
|
||||
}
|
||||
}
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
ndkVersion = Versions.NDK_VERSION
|
||||
ndkVersion = AndroidVersions.NDK_VERSION
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/androidMain/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(11)
|
||||
jvmToolchain(17)
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
@@ -178,38 +242,34 @@ android {
|
||||
unitTests {
|
||||
isReturnDefaultValues = true
|
||||
isIncludeAndroidResources = true
|
||||
all {
|
||||
it.exclude("**/screen/**")
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
debugImplementation(compose.uiTooling)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("kspCommonMainMetadata", libs.koin.kspCompiler)
|
||||
//add("kspAndroid", libs.koin.kspCompiler)
|
||||
//add("kspIosX64", libs.koin.kspCompiler)
|
||||
//add("kspIosArm64", libs.koin.kspCompiler)
|
||||
//add("kspIosSimulatorArm64", libs.koin.kspCompiler)
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
if (name != "kspCommonMainKotlinMetadata") {
|
||||
dependsOn("kspCommonMainKotlinMetadata")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.filter {
|
||||
it.name.contains("compileKotlinIos", true)
|
||||
|| it.name.contains("compileTestKotlinIos", true)
|
||||
}.forEach {
|
||||
it.dependsOn("kspCommonMainKotlinMetadata")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.filter {
|
||||
it.name.contains("SourcesJar", true)
|
||||
}.forEach {
|
||||
it.dependsOn("kspCommonMainKotlinMetadata")
|
||||
}
|
||||
|
||||
kover {
|
||||
reports {
|
||||
variant("debug") {
|
||||
filters {
|
||||
includes {
|
||||
packages(KoverConfig.includedPackages)
|
||||
}
|
||||
excludes {
|
||||
packages(KoverConfig.excludedPackages)
|
||||
classes(KoverConfig.excludedClasses)
|
||||
annotatedBy(KoverConfig.excludedAnnotations)
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
htmlDir = layout.buildDirectory.dir("coverage-report/html")
|
||||
}
|
||||
|
||||
xml {
|
||||
xmlFile = layout.buildDirectory.file("coverage-report/result.xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,4 @@ class AppInitializerComponent : KoinComponent {
|
||||
fun init(application: Application) {
|
||||
nativeToolsInitializer.init(application)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,13 @@ 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(createdAtStart = true)
|
||||
class NativeToolsInitializer(
|
||||
private val cpuDataNativeProvider: CpuDataNativeProvider
|
||||
private val cpuDataNativeProvider: CpuDataNativeProvider,
|
||||
) {
|
||||
|
||||
fun init(application: Application) {
|
||||
ReLinker.loadLibrary(application, "cpuinfo-libs")
|
||||
cpuDataNativeProvider.initLibrary()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
package com.kgurgul.cpuinfo.components
|
||||
|
||||
import android.os.Build
|
||||
import org.koin.core.annotation.Factory
|
||||
|
||||
@Factory
|
||||
actual class RamCleanupComponent actual constructor() {
|
||||
actual class RamCleanupComponent actual constructor() : IRamCleanupComponent {
|
||||
|
||||
actual fun cleanup() {
|
||||
actual override fun cleanup() {
|
||||
System.runFinalization()
|
||||
Runtime.getRuntime().gc()
|
||||
System.gc()
|
||||
}
|
||||
|
||||
actual fun isCleanupActionAvailable(): Boolean {
|
||||
actual override fun isCleanupActionAvailable(): Boolean {
|
||||
return Build.VERSION.SDK_INT < 24
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,46 +2,51 @@ package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import com.kgurgul.cpuinfo.domain.model.ExtendedApplicationData
|
||||
import org.koin.core.annotation.Factory
|
||||
import java.io.File
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
|
||||
@Factory
|
||||
actual class ApplicationsDataProvider actual constructor() : KoinComponent {
|
||||
actual class ApplicationsDataProvider actual constructor() :
|
||||
IApplicationsDataProvider,
|
||||
KoinComponent {
|
||||
|
||||
private val packageManager: PackageManager by inject()
|
||||
|
||||
actual fun getInstalledApplications(withSystemApps: Boolean): List<ExtendedApplicationData> {
|
||||
val applications = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getInstalledApplications(
|
||||
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())
|
||||
)
|
||||
} else {
|
||||
packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
|
||||
}
|
||||
actual override fun getInstalledApplications(
|
||||
withSystemApps: Boolean,
|
||||
): List<ExtendedApplicationData> {
|
||||
val packages = packageManager.getInstalledPackages(0)
|
||||
return if (withSystemApps) {
|
||||
applications
|
||||
packages
|
||||
} else {
|
||||
applications.filter { (it.flags and ApplicationInfo.FLAG_SYSTEM) == 0 }
|
||||
packages.filter {
|
||||
val applicationInfo = it.applicationInfo
|
||||
applicationInfo != null
|
||||
&& (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0
|
||||
}
|
||||
}.map {
|
||||
ExtendedApplicationData(
|
||||
it.loadLabel(packageManager).toString(),
|
||||
it.packageName,
|
||||
it.sourceDir,
|
||||
getNativeLibs(it.nativeLibraryDir),
|
||||
it.hasNativeLibs(),
|
||||
getAppIconUri(it.packageName)
|
||||
name = it.applicationInfo?.loadLabel(packageManager)?.toString() ?: "",
|
||||
packageName = it.packageName,
|
||||
versionName = it.versionName ?: "N/A",
|
||||
nativeLibs = getNativeLibs(it.applicationInfo?.nativeLibraryDir),
|
||||
hasNativeLibs = it.applicationInfo?.hasNativeLibs() ?: false,
|
||||
appIconUri = getAppIconUri(it.packageName),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun areApplicationsSupported() = true
|
||||
actual override fun areApplicationsSupported() = true
|
||||
|
||||
actual override fun hasSystemAppsFiltering() = true
|
||||
|
||||
actual override fun hasAppManagementSupported() = true
|
||||
|
||||
actual override fun hasManualRefresh() = false
|
||||
|
||||
private fun ApplicationInfo.hasNativeLibs(): Boolean {
|
||||
return if (nativeLibraryDir != null) {
|
||||
@@ -63,17 +68,16 @@ actual class ApplicationsDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
|
||||
private fun getResourceId(packageName: String): Int {
|
||||
val packageInfo: PackageInfo
|
||||
try {
|
||||
packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
return try {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
|
||||
} else {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
}
|
||||
packageInfo.applicationInfo?.icon ?: 0
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return 0
|
||||
0
|
||||
}
|
||||
return packageInfo.applicationInfo.icon
|
||||
}
|
||||
|
||||
private fun getNativeLibs(nativeLibsDir: String?): List<String> {
|
||||
@@ -83,4 +87,4 @@ actual class ApplicationsDataProvider actual constructor() : KoinComponent {
|
||||
val nativeDirFile = File(nativeLibsDir)
|
||||
return nativeDirFile.listFiles()?.map { it.name } ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import org.koin.core.annotation.Single
|
||||
actual class CpuDataNativeProvider actual constructor() : ICpuDataNativeProvider {
|
||||
|
||||
@Single(createdAtStart = true)
|
||||
actual class CpuDataNativeProvider actual constructor() {
|
||||
actual external override fun initLibrary()
|
||||
|
||||
actual external fun initLibrary()
|
||||
actual external override fun getCpuName(): String
|
||||
|
||||
actual external fun getCpuName(): String
|
||||
actual external override fun hasArmNeon(): Boolean
|
||||
|
||||
actual external fun hasArmNeon(): Boolean
|
||||
actual external override fun getL1dCaches(): IntArray?
|
||||
|
||||
actual external fun getL1dCaches(): IntArray?
|
||||
actual external override fun getL1iCaches(): IntArray?
|
||||
|
||||
actual external fun getL1iCaches(): IntArray?
|
||||
actual external override fun getL2Caches(): IntArray?
|
||||
|
||||
actual external fun getL2Caches(): IntArray?
|
||||
actual external override fun getL3Caches(): IntArray?
|
||||
|
||||
actual external fun getL3Caches(): IntArray?
|
||||
actual external override fun getL4Caches(): IntArray?
|
||||
|
||||
actual external fun getL4Caches(): IntArray?
|
||||
|
||||
actual external fun getNumberOfCores(): Int
|
||||
}
|
||||
actual external override fun getNumberOfCores(): Int
|
||||
}
|
||||
|
||||
@@ -2,17 +2,15 @@ package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import android.os.Build
|
||||
import com.kgurgul.cpuinfo.utils.CpuLogger
|
||||
import org.koin.core.annotation.Factory
|
||||
import java.io.RandomAccessFile
|
||||
|
||||
@Factory
|
||||
actual class CpuDataProvider actual constructor() {
|
||||
actual class CpuDataProvider actual constructor() : ICpuDataProvider {
|
||||
|
||||
actual fun getAbi(): String {
|
||||
actual override fun getAbi(): String {
|
||||
return Build.SUPPORTED_ABIS[0]
|
||||
}
|
||||
|
||||
actual fun getNumberOfCores(): Int {
|
||||
actual override fun getNumberOfCores(): Int {
|
||||
return Runtime.getRuntime().availableProcessors()
|
||||
}
|
||||
|
||||
@@ -20,7 +18,7 @@ actual class CpuDataProvider actual constructor() {
|
||||
* Checking frequencies directories and return current value if exists (otherwise we can
|
||||
* assume that core is stopped - value -1)
|
||||
*/
|
||||
actual fun getCurrentFreq(coreNumber: Int): Long {
|
||||
actual override fun getCurrentFreq(coreNumber: Int): Long {
|
||||
val currentFreqPath = "${CPU_INFO_DIR}cpu$coreNumber/cpufreq/scaling_cur_freq"
|
||||
return try {
|
||||
RandomAccessFile(currentFreqPath, "r").use { it.readLine().toLong() / 1000 }
|
||||
@@ -34,7 +32,7 @@ actual class CpuDataProvider actual constructor() {
|
||||
* Read max/min frequencies for specific [coreNumber]. Return [Pair] with min and max frequency
|
||||
* or [Pair] with -1.
|
||||
*/
|
||||
actual fun getMinMaxFreq(coreNumber: Int): Pair<Long, Long> {
|
||||
actual override fun getMinMaxFreq(coreNumber: Int): Pair<Long, Long> {
|
||||
val minPath = "${CPU_INFO_DIR}cpu$coreNumber/cpufreq/cpuinfo_min_freq"
|
||||
val maxPath = "${CPU_INFO_DIR}cpu$coreNumber/cpufreq/cpuinfo_max_freq"
|
||||
return try {
|
||||
@@ -50,4 +48,4 @@ actual class CpuDataProvider actual constructor() {
|
||||
companion object {
|
||||
private const val CPU_INFO_DIR = "/sys/devices/system/cpu/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,31 +3,27 @@ package com.kgurgul.cpuinfo.data.provider
|
||||
import android.app.ActivityManager
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import com.kgurgul.cpuinfo.domain.model.ItemValue
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.gles_version
|
||||
import com.kgurgul.cpuinfo.shared.unknown
|
||||
import com.kgurgul.cpuinfo.shared.vulkan_version
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class GpuDataProvider actual constructor() : KoinComponent {
|
||||
actual class GpuDataProvider actual constructor() : IGpuDataProvider, KoinComponent {
|
||||
|
||||
private val activityManager: ActivityManager by inject()
|
||||
private val packageManager: PackageManager by inject()
|
||||
|
||||
actual suspend fun getData(): List<Pair<String, String>> {
|
||||
actual override suspend fun getData(): List<ItemValue> {
|
||||
return buildList {
|
||||
val vulcanVersion = getVulkanVersion()
|
||||
if (vulcanVersion.isNotEmpty()) {
|
||||
add(getString(Res.string.vulkan_version) to vulcanVersion)
|
||||
add(ItemValue.NameResource(Res.string.vulkan_version, vulcanVersion))
|
||||
}
|
||||
val glVersion = getGlEsVersion()
|
||||
if (glVersion.isNotEmpty()) {
|
||||
add(getString(Res.string.gles_version) to glVersion)
|
||||
add(ItemValue.NameResource(Res.string.gles_version, glVersion))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +33,7 @@ actual class GpuDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
|
||||
private fun getVulkanVersion(): String {
|
||||
val default = runBlocking { getString(Res.string.unknown) }
|
||||
val default = ""
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
return default
|
||||
}
|
||||
@@ -51,9 +47,9 @@ actual class GpuDataProvider actual constructor() : KoinComponent {
|
||||
|
||||
// Extract versions from bit field
|
||||
// See: https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_VULKAN_HARDWARE_VERSION
|
||||
val major = vulkan shr 22 // Higher 10 bits
|
||||
val minor = vulkan shl 10 shr 22 // Middle 10 bits
|
||||
val patch = vulkan shl 20 shr 22 // Lower 12 bits
|
||||
val major = vulkan shr 22 // Higher 10 bits
|
||||
val minor = vulkan shl 10 shr 22 // Middle 10 bits
|
||||
val patch = vulkan shl 20 shr 22 // Lower 12 bits
|
||||
//
|
||||
return "$major.$minor.$patch"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.hardware.camera2.CameraManager
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
import com.kgurgul.cpuinfo.domain.model.ItemValue
|
||||
import com.kgurgul.cpuinfo.features.temperature.TemperatureFormatter
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.amount
|
||||
@@ -52,6 +53,14 @@ import com.kgurgul.cpuinfo.shared.capacity
|
||||
import com.kgurgul.cpuinfo.shared.card
|
||||
import com.kgurgul.cpuinfo.shared.charging_type
|
||||
import com.kgurgul.cpuinfo.shared.front
|
||||
import com.kgurgul.cpuinfo.shared.hardware_ac
|
||||
import com.kgurgul.cpuinfo.shared.hardware_camera_lens_format
|
||||
import com.kgurgul.cpuinfo.shared.hardware_camera_name_format
|
||||
import com.kgurgul.cpuinfo.shared.hardware_camera_orientation_format
|
||||
import com.kgurgul.cpuinfo.shared.hardware_camera_type_format
|
||||
import com.kgurgul.cpuinfo.shared.hardware_otg
|
||||
import com.kgurgul.cpuinfo.shared.hardware_sound_card_format
|
||||
import com.kgurgul.cpuinfo.shared.hardware_usb
|
||||
import com.kgurgul.cpuinfo.shared.ir_emitter
|
||||
import com.kgurgul.cpuinfo.shared.is_charging
|
||||
import com.kgurgul.cpuinfo.shared.level
|
||||
@@ -68,45 +77,42 @@ import com.kgurgul.cpuinfo.shared.wireless
|
||||
import com.kgurgul.cpuinfo.shared.yes
|
||||
import com.kgurgul.cpuinfo.utils.CpuLogger
|
||||
import com.kgurgul.cpuinfo.utils.round2
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
import java.io.FileFilter
|
||||
import java.io.RandomAccessFile
|
||||
import java.util.regex.Pattern
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
|
||||
private val appContext: Context by inject()
|
||||
private val temperatureProvider: TemperatureProvider by inject()
|
||||
private val temperatureProvider: ITemperatureProvider by inject()
|
||||
private val temperatureFormatter: TemperatureFormatter by inject()
|
||||
private val packageManager: PackageManager by inject()
|
||||
private val contentResolver: ContentResolver by inject()
|
||||
private val wifiManager: WifiManager by inject()
|
||||
private val cameraManager: CameraManager by inject()
|
||||
|
||||
actual suspend fun getData(): List<Pair<String, String>> {
|
||||
actual suspend fun getData(): List<ItemValue> {
|
||||
return buildList {
|
||||
add(Pair(getString(Res.string.battery), ""))
|
||||
add(ItemValue.NameResource(Res.string.battery, ""))
|
||||
addAll(getBatteryStatus())
|
||||
|
||||
if (hasCamera()) {
|
||||
add(Pair(getString(Res.string.cameras), ""))
|
||||
add(ItemValue.NameResource(Res.string.cameras, ""))
|
||||
addAll(getCameraInfo())
|
||||
}
|
||||
|
||||
add(Pair(getString(Res.string.sound_card), ""))
|
||||
add(ItemValue.NameResource(Res.string.sound_card, ""))
|
||||
addAll(getSoundCardInfo())
|
||||
addAll(getWirelessInfo())
|
||||
addAll(getUsbInfo())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getBatteryStatus(): ArrayList<Pair<String, String>> {
|
||||
val functionsList = ArrayList<Pair<String, String>>()
|
||||
private suspend fun getBatteryStatus(): List<ItemValue> {
|
||||
val functionsList = mutableListOf<ItemValue>()
|
||||
|
||||
val batteryStatus = getBatteryStatusIntent()
|
||||
if (batteryStatus != null) {
|
||||
@@ -117,10 +123,10 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
if (level != -1 && scale != -1) {
|
||||
val batteryPct = level / scale.toFloat() * 100.0
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.level),
|
||||
"${batteryPct.round2()}%"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.level,
|
||||
"${batteryPct.round2()}%",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,10 +134,10 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
val health = batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)
|
||||
if (health != -1) {
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.battery_health),
|
||||
getBatteryHealthStatus(health)
|
||||
)
|
||||
ItemValue.NameValueResource(
|
||||
Res.string.battery_health,
|
||||
getBatteryHealthStatus(health),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,10 +145,10 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
val voltage = batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
|
||||
if (voltage > 0) {
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.voltage),
|
||||
"${voltage / 1000.0}V"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.voltage,
|
||||
"${voltage / 1000.0}V",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -151,17 +157,17 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
val temperature = temperatureProvider.getBatteryTemperature()
|
||||
if (temperature != null) {
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.temperature),
|
||||
temperatureFormatter.format(temperature)
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.temperature,
|
||||
temperatureFormatter.format(temperature),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Capacity
|
||||
val capacity = getBatteryCapacity().round2()
|
||||
if (capacity != -1.0) {
|
||||
functionsList.add(Pair(getString(Res.string.capacity), "${capacity}mAh"))
|
||||
functionsList.add(ItemValue.NameResource(Res.string.capacity, "${capacity}mAh"))
|
||||
}
|
||||
|
||||
if (batteryStatus != null) {
|
||||
@@ -169,47 +175,47 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
val technology = batteryStatus.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY)
|
||||
if (!technology.isNullOrEmpty()) {
|
||||
functionsList.add(
|
||||
Pair(getString(Res.string.technology), technology)
|
||||
ItemValue.NameResource(Res.string.technology, technology),
|
||||
)
|
||||
}
|
||||
|
||||
// Are we charging / is charged?
|
||||
val status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL
|
||||
status == BatteryManager.BATTERY_STATUS_FULL
|
||||
|
||||
// How we charging?
|
||||
val chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
|
||||
val usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB
|
||||
val acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC
|
||||
|
||||
val charging =
|
||||
if (isCharging) getString(Res.string.yes)
|
||||
else getString(Res.string.no)
|
||||
functionsList.add(Pair(getString(Res.string.is_charging), charging))
|
||||
val charging = getYesNoStringResource(isCharging)
|
||||
functionsList.add(ItemValue.NameValueResource(Res.string.is_charging, charging))
|
||||
if (isCharging) {
|
||||
val chargingType: String = when {
|
||||
usbCharge -> "USB"
|
||||
acCharge -> "AC"
|
||||
else -> getString(Res.string.unknown)
|
||||
val chargingType = when {
|
||||
usbCharge -> Res.string.hardware_usb
|
||||
acCharge -> Res.string.hardware_ac
|
||||
else -> Res.string.unknown
|
||||
}
|
||||
functionsList.add(Pair(getString(Res.string.charging_type), chargingType))
|
||||
functionsList.add(
|
||||
ItemValue.NameValueResource(Res.string.charging_type, chargingType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return functionsList
|
||||
}
|
||||
|
||||
private suspend fun getBatteryHealthStatus(healthInt: Int): String {
|
||||
private fun getBatteryHealthStatus(healthInt: Int): StringResource {
|
||||
return when (healthInt) {
|
||||
BatteryManager.BATTERY_HEALTH_COLD -> getString(Res.string.battery_cold)
|
||||
BatteryManager.BATTERY_HEALTH_GOOD -> getString(Res.string.battery_good)
|
||||
BatteryManager.BATTERY_HEALTH_DEAD -> getString(Res.string.battery_dead)
|
||||
BatteryManager.BATTERY_HEALTH_OVERHEAT -> getString(Res.string.battery_overheat)
|
||||
BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> getString(Res.string.battery_overvoltage)
|
||||
BatteryManager.BATTERY_HEALTH_UNKNOWN -> getString(Res.string.battery_unknown)
|
||||
BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE -> getString(Res.string.battery_unspecified_failure)
|
||||
else -> getString(Res.string.battery_unknown)
|
||||
BatteryManager.BATTERY_HEALTH_COLD -> Res.string.battery_cold
|
||||
BatteryManager.BATTERY_HEALTH_GOOD -> Res.string.battery_good
|
||||
BatteryManager.BATTERY_HEALTH_DEAD -> Res.string.battery_dead
|
||||
BatteryManager.BATTERY_HEALTH_OVERHEAT -> Res.string.battery_overheat
|
||||
BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> Res.string.battery_overvoltage
|
||||
BatteryManager.BATTERY_HEALTH_UNKNOWN -> Res.string.battery_unknown
|
||||
BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE -> Res.string.battery_unspecified_failure
|
||||
else -> Res.string.battery_unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,66 +223,97 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
* Get Wi-Fi and Bluetooth mac address and Bluetooth LE support
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
private suspend fun getWirelessInfo(): List<Pair<String, String>> {
|
||||
val functionsList = mutableListOf<Pair<String, String>>()
|
||||
functionsList.add(getString(Res.string.wireless) to "")
|
||||
private fun getWirelessInfo(): List<ItemValue> {
|
||||
val functionsList = mutableListOf<ItemValue>()
|
||||
functionsList.add(ItemValue.NameResource(Res.string.wireless, ""))
|
||||
// Bluetooth
|
||||
functionsList.add(
|
||||
getString(Res.string.bluetooth) to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
|
||||
)
|
||||
ItemValue.NameValueResource(
|
||||
Res.string.bluetooth,
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH),
|
||||
)
|
||||
),
|
||||
)
|
||||
val hasBluetoothLe = getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
||||
val hasBluetoothLe = getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE),
|
||||
)
|
||||
functionsList.add(getString(Res.string.bluetooth_le) to hasBluetoothLe)
|
||||
functionsList.add(ItemValue.NameValueResource(Res.string.bluetooth_le, hasBluetoothLe))
|
||||
// GPS
|
||||
functionsList.add(
|
||||
"GPS" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)
|
||||
)
|
||||
ItemValue.ValueResource(
|
||||
"GPS",
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS),
|
||||
)
|
||||
),
|
||||
)
|
||||
// NFC
|
||||
functionsList.add(
|
||||
"NFC" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)
|
||||
)
|
||||
ItemValue.ValueResource(
|
||||
"NFC",
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC),
|
||||
)
|
||||
),
|
||||
)
|
||||
functionsList.add(
|
||||
"NFC Card Emulation" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
|
||||
)
|
||||
ItemValue.ValueResource(
|
||||
"NFC Card Emulation",
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION),
|
||||
)
|
||||
),
|
||||
)
|
||||
// Wi-Fi
|
||||
val hasWiFi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)
|
||||
functionsList.add("Wi-Fi" to getYesNoString(hasWiFi))
|
||||
functionsList.add(ItemValue.ValueResource("Wi-Fi", getYesNoStringResource(hasWiFi)))
|
||||
if (hasWiFi) {
|
||||
functionsList.add(
|
||||
"Wi-Fi Aware" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
|
||||
ItemValue.ValueResource(
|
||||
"Wi-Fi Aware", getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE),
|
||||
)
|
||||
),
|
||||
)
|
||||
functionsList.add(
|
||||
ItemValue.ValueResource(
|
||||
"Wi-Fi Direct",
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT),
|
||||
)
|
||||
),
|
||||
)
|
||||
functionsList.add(
|
||||
ItemValue.ValueResource(
|
||||
"Wi-Fi Passpoint",
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT),
|
||||
)
|
||||
)
|
||||
)
|
||||
functionsList.add(
|
||||
"Wi-Fi Direct" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)
|
||||
ItemValue.ValueResource(
|
||||
"Wi-Fi 5Ghz",
|
||||
getYesNoStringResource(wifiManager.is5GHzBandSupported)
|
||||
)
|
||||
)
|
||||
functionsList.add(
|
||||
"Wi-Fi Passpoint" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT)
|
||||
ItemValue.ValueResource(
|
||||
"Wi-Fi P2P",
|
||||
getYesNoStringResource(wifiManager.isP2pSupported)
|
||||
)
|
||||
)
|
||||
functionsList.add("Wi-Fi 5Ghz" to getYesNoString(wifiManager.is5GHzBandSupported))
|
||||
functionsList.add("Wi-Fi P2P" to getYesNoString(wifiManager.isP2pSupported))
|
||||
}
|
||||
|
||||
try {
|
||||
val bluetoothMac = android.provider.Settings.Secure.getString(
|
||||
contentResolver,
|
||||
"bluetooth_address"
|
||||
"bluetooth_address",
|
||||
)
|
||||
if (bluetoothMac != null && bluetoothMac.isNotEmpty())
|
||||
functionsList.add(getString(Res.string.bluetooth_mac) to bluetoothMac)
|
||||
if (bluetoothMac != null && bluetoothMac.isNotEmpty()) {
|
||||
functionsList.add(ItemValue.NameResource(Res.string.bluetooth_mac, bluetoothMac))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// ignored
|
||||
}
|
||||
@@ -284,10 +321,9 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
// Wi-Fi mac
|
||||
val filePath = "/sys/class/net/wlan0/address"
|
||||
try {
|
||||
val reader = RandomAccessFile(filePath, "r")
|
||||
val value = reader.readLine()
|
||||
reader.close()
|
||||
functionsList.add(getString(Res.string.wifi_mac) to value)
|
||||
File(filePath).readLines().firstOrNull()?.let {
|
||||
functionsList.add(ItemValue.NameResource(Res.string.wifi_mac, it))
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
@@ -295,20 +331,25 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
val irManager = appContext
|
||||
.getSystemService(Context.CONSUMER_IR_SERVICE) as? ConsumerIrManager?
|
||||
val hasIr = irManager?.hasIrEmitter() ?: false
|
||||
functionsList.add(getString(Res.string.ir_emitter) to getYesNoString(hasIr))
|
||||
functionsList.add(
|
||||
ItemValue.NameValueResource(Res.string.ir_emitter, getYesNoStringResource(hasIr))
|
||||
)
|
||||
|
||||
return functionsList
|
||||
}
|
||||
|
||||
private suspend fun getUsbInfo(): List<Pair<String, String>> {
|
||||
val featureList = mutableListOf<Pair<String, String>>()
|
||||
featureList.add("USB" to "")
|
||||
featureList.add(
|
||||
"OTG" to getYesNoString(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
||||
private fun getUsbInfo(): List<ItemValue> {
|
||||
return buildList {
|
||||
add(ItemValue.NameResource(Res.string.hardware_usb, ""))
|
||||
add(
|
||||
ItemValue.NameValueResource(
|
||||
Res.string.hardware_otg,
|
||||
getYesNoStringResource(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return featureList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,68 +372,47 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
/**
|
||||
* Get available data connected with sound card like ALSA version etc.
|
||||
*/
|
||||
private suspend fun getSoundCardInfo(): List<Pair<String, String>> {
|
||||
val functionsList = mutableListOf<Pair<String, String>>()
|
||||
private fun getSoundCardInfo(): List<ItemValue> {
|
||||
val functionsList = mutableListOf<ItemValue>()
|
||||
|
||||
val soundCardNumber = getSoundCardNumber()
|
||||
functionsList.add(Pair(getString(Res.string.amount), soundCardNumber.toString()))
|
||||
functionsList.add(ItemValue.NameResource(Res.string.amount, soundCardNumber.toString()))
|
||||
for (i in 0 until soundCardNumber) {
|
||||
functionsList.add(
|
||||
Pair(
|
||||
" ${getString(Res.string.card)} $i",
|
||||
tryToGetSoundCardId(i)
|
||||
tryToGetSoundCardId(i)?.let {
|
||||
functionsList.add(
|
||||
ItemValue.FormattedNameResource(
|
||||
Res.string.hardware_sound_card_format,
|
||||
listOf(Res.string.card, i),
|
||||
it,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// ALSA
|
||||
val alsa = tryToGetAlsa()
|
||||
if (!alsa.isNullOrEmpty()) {
|
||||
functionsList.add(Pair("ALSA", alsa))
|
||||
functionsList.add(ItemValue.Text("ALSA", alsa))
|
||||
}
|
||||
|
||||
return functionsList
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read id of the sound card.
|
||||
*
|
||||
* @param cardPosition position of the sound card in files
|
||||
* @return id from "id" file
|
||||
*/
|
||||
private suspend fun tryToGetSoundCardId(cardPosition: Int): String {
|
||||
var id = getString(Res.string.unknown)
|
||||
private fun tryToGetSoundCardId(cardPosition: Int): String? {
|
||||
val filePath = "/proc/asound/card$cardPosition/id"
|
||||
|
||||
var reader: RandomAccessFile? = null
|
||||
try {
|
||||
reader = RandomAccessFile(filePath, "r")
|
||||
id = reader.readLine()
|
||||
} catch (ignored: Exception) {
|
||||
} finally {
|
||||
reader?.close()
|
||||
return try {
|
||||
File(filePath).readLines().firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ALSA version if exists, otherwise null
|
||||
*/
|
||||
private fun tryToGetAlsa(): String? {
|
||||
var alsa: String? = null
|
||||
val filePath = "/proc/asound/version"
|
||||
|
||||
var reader: RandomAccessFile? = null
|
||||
try {
|
||||
reader = RandomAccessFile(filePath, "r")
|
||||
val version = reader.readLine()
|
||||
alsa = version
|
||||
} catch (ignored: Exception) {
|
||||
} finally {
|
||||
reader?.close()
|
||||
return try {
|
||||
File(filePath).readLines().firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
return alsa
|
||||
}
|
||||
|
||||
private fun hasCamera(): Boolean =
|
||||
@@ -401,37 +421,59 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
/**
|
||||
* Get number, type and orientation of the cameras
|
||||
*/
|
||||
private suspend fun getCameraInfo(): List<Pair<String, String>> {
|
||||
val functionsList = mutableListOf<Pair<String, String>>()
|
||||
private fun getCameraInfo(): List<ItemValue> {
|
||||
val functionsList = mutableListOf<ItemValue>()
|
||||
|
||||
try {
|
||||
val cameraIdList = cameraManager.cameraIdList
|
||||
val numberOfCameras = cameraIdList.size
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.amount),
|
||||
numberOfCameras.toString()
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.amount,
|
||||
numberOfCameras.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
val cameraName = getString(Res.string.camera)
|
||||
val cameraType = getString(Res.string.type)
|
||||
val cameraOrientation = getString(Res.string.orientation)
|
||||
for (cameraId in cameraIdList) {
|
||||
functionsList.add(Pair(" $cameraName $cameraId", " "))
|
||||
functionsList.add(
|
||||
ItemValue.FormattedNameResource(
|
||||
Res.string.hardware_camera_name_format,
|
||||
listOf(Res.string.camera, cameraId),
|
||||
" ",
|
||||
)
|
||||
)
|
||||
try {
|
||||
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
|
||||
val orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
|
||||
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
functionsList.add(Pair(" $cameraType", getCameraFacing(facing)))
|
||||
functionsList.add(Pair(" $cameraOrientation", orientation.toString()))
|
||||
functionsList.add(
|
||||
ItemValue.FormattedNameValueResource(
|
||||
Res.string.hardware_camera_type_format,
|
||||
listOf(Res.string.type),
|
||||
getCameraFacing(facing),
|
||||
)
|
||||
)
|
||||
functionsList.add(
|
||||
ItemValue.FormattedNameResource(
|
||||
Res.string.hardware_camera_orientation_format,
|
||||
listOf(Res.string.orientation),
|
||||
orientation.toString(),
|
||||
)
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
val lensAmount = characteristics.physicalCameraIds.size
|
||||
if (lensAmount > 0) {
|
||||
functionsList.add(
|
||||
Pair(
|
||||
" ${getString(Res.string.camera_lens_number)}",
|
||||
lensAmount.toString()
|
||||
ItemValue.FormattedNameResource(
|
||||
Res.string.hardware_camera_orientation_format,
|
||||
listOf(Res.string.orientation),
|
||||
orientation.toString(),
|
||||
)
|
||||
)
|
||||
functionsList.add(
|
||||
ItemValue.FormattedNameResource(
|
||||
Res.string.hardware_camera_lens_format,
|
||||
listOf(Res.string.camera_lens_number),
|
||||
lensAmount.toString(),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -442,30 +484,24 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
CpuLogger.e(e) { "Cannot read camera list" }
|
||||
functionsList.add(Pair(getString(Res.string.amount), "0"))
|
||||
functionsList.add(ItemValue.NameResource(Res.string.amount, "0"))
|
||||
}
|
||||
|
||||
return functionsList
|
||||
}
|
||||
|
||||
private suspend fun getCameraFacing(facing: Int?): String =
|
||||
private fun getCameraFacing(facing: Int?): StringResource =
|
||||
when (facing) {
|
||||
CameraCharacteristics.LENS_FACING_FRONT ->
|
||||
getString(Res.string.front)
|
||||
|
||||
CameraCharacteristics.LENS_FACING_BACK ->
|
||||
getString(Res.string.back)
|
||||
|
||||
CameraCharacteristics.LENS_FACING_EXTERNAL ->
|
||||
getString(Res.string.camera_external)
|
||||
|
||||
else -> getString(Res.string.unknown)
|
||||
CameraCharacteristics.LENS_FACING_FRONT -> Res.string.front
|
||||
CameraCharacteristics.LENS_FACING_BACK -> Res.string.back
|
||||
CameraCharacteristics.LENS_FACING_EXTERNAL -> Res.string.camera_external
|
||||
else -> Res.string.unknown
|
||||
}
|
||||
|
||||
private suspend fun getYesNoString(yesValue: Boolean) = if (yesValue) {
|
||||
getString(Res.string.yes)
|
||||
private fun getYesNoStringResource(yesValue: Boolean) = if (yesValue) {
|
||||
Res.string.yes
|
||||
} else {
|
||||
getString(Res.string.no)
|
||||
Res.string.no
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,4 +530,4 @@ actual class HardwareDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
return capacity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import com.kgurgul.cpuinfo.domain.model.ItemValue
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.android_id
|
||||
import com.kgurgul.cpuinfo.shared.board
|
||||
@@ -18,40 +19,45 @@ import com.kgurgul.cpuinfo.shared.google_services_framework_id
|
||||
import com.kgurgul.cpuinfo.shared.kernel
|
||||
import com.kgurgul.cpuinfo.shared.manufacturer
|
||||
import com.kgurgul.cpuinfo.shared.model
|
||||
import com.kgurgul.cpuinfo.shared.no
|
||||
import com.kgurgul.cpuinfo.shared.os_language
|
||||
import com.kgurgul.cpuinfo.shared.rooted
|
||||
import com.kgurgul.cpuinfo.shared.sdk
|
||||
import com.kgurgul.cpuinfo.shared.security_providers
|
||||
import com.kgurgul.cpuinfo.shared.serial
|
||||
import com.kgurgul.cpuinfo.shared.strongbox
|
||||
import com.kgurgul.cpuinfo.shared.tab_os
|
||||
import com.kgurgul.cpuinfo.shared.unknown
|
||||
import com.kgurgul.cpuinfo.shared.version
|
||||
import com.kgurgul.cpuinfo.shared.vm
|
||||
import com.kgurgul.cpuinfo.shared.yes
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import com.kgurgul.cpuinfo.utils.ResourceUtils
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
import java.security.Security
|
||||
import java.util.Locale
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
actual class OsDataProvider actual constructor() :
|
||||
IOsDataProvider,
|
||||
KoinComponent {
|
||||
|
||||
private val contentResolver: ContentResolver by inject()
|
||||
private val packageManager: PackageManager by inject()
|
||||
private val devicePolicyManager: DevicePolicyManager by inject()
|
||||
|
||||
actual suspend fun getData(): List<Pair<String, String>> {
|
||||
actual override suspend fun getData(): List<ItemValue> {
|
||||
return buildList {
|
||||
add(getString(Res.string.tab_os) to "Android")
|
||||
add(ItemValue.NameResource(Res.string.tab_os, "Android"))
|
||||
addAll(getBuildData())
|
||||
add(ItemValue.NameResource(Res.string.os_language, Locale.getDefault().language))
|
||||
getAndroidIdData()?.let { add(it) }
|
||||
getGsfAndroidId()?.let { add(it) }
|
||||
add(getString(Res.string.rooted) to getYesNoString(isDeviceRooted()))
|
||||
add(
|
||||
ItemValue.NameValueResource(
|
||||
Res.string.rooted,
|
||||
ResourceUtils.getYesNoStringResource(isDeviceRooted())
|
||||
)
|
||||
)
|
||||
getDeviceEncryptionStatus()?.let { add(it) }
|
||||
add(getStrongBoxData())
|
||||
addAll(getSecurityData())
|
||||
@@ -62,20 +68,20 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
* Retrieve data from static Build class and system property "java.vm.version"
|
||||
*/
|
||||
@SuppressLint("HardwareIds")
|
||||
private suspend fun getBuildData(): List<Pair<String, String>> {
|
||||
private fun getBuildData(): List<ItemValue> {
|
||||
return buildList {
|
||||
add(getString(Res.string.version) to Build.VERSION.RELEASE)
|
||||
add(getString(Res.string.sdk) to Build.VERSION.SDK_INT.toString())
|
||||
add(getString(Res.string.codename) to Build.VERSION.CODENAME)
|
||||
add(getString(Res.string.bootloader) to Build.BOOTLOADER)
|
||||
add(getString(Res.string.brand) to Build.BRAND)
|
||||
add(getString(Res.string.model) to Build.MODEL)
|
||||
add(getString(Res.string.manufacturer) to Build.MANUFACTURER)
|
||||
add(getString(Res.string.board) to Build.BOARD)
|
||||
add(getString(Res.string.vm) to getVmVersion())
|
||||
add(getString(Res.string.kernel) to (System.getProperty("os.version") ?: ""))
|
||||
add(ItemValue.NameResource(Res.string.version, Build.VERSION.RELEASE))
|
||||
add(ItemValue.NameResource(Res.string.sdk, Build.VERSION.SDK_INT.toString()))
|
||||
add(ItemValue.NameResource(Res.string.codename, Build.VERSION.CODENAME))
|
||||
add(ItemValue.NameResource(Res.string.bootloader, Build.BOOTLOADER))
|
||||
add(ItemValue.NameResource(Res.string.brand, Build.BRAND))
|
||||
add(ItemValue.NameResource(Res.string.model, Build.MODEL))
|
||||
add(ItemValue.NameResource(Res.string.manufacturer, Build.MANUFACTURER))
|
||||
add(ItemValue.NameResource(Res.string.board, Build.BOARD))
|
||||
add(ItemValue.NameResource(Res.string.vm, getVmVersion()))
|
||||
add(ItemValue.NameResource(Res.string.kernel, (System.getProperty("os.version") ?: "")))
|
||||
@Suppress("DEPRECATION")
|
||||
add(getString(Res.string.serial) to Build.SERIAL)
|
||||
add(ItemValue.NameResource(Res.string.serial, Build.SERIAL))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +89,10 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
* Get AndroidID. Keep in mind that from Android O it is unique per app.
|
||||
*/
|
||||
@SuppressLint("HardwareIds")
|
||||
private suspend fun getAndroidIdData(): Pair<String, String>? {
|
||||
private fun getAndroidIdData(): ItemValue? {
|
||||
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
|
||||
return if (androidId != null) {
|
||||
getString(Res.string.android_id) to androidId
|
||||
ItemValue.NameResource(Res.string.android_id, androidId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -96,7 +102,7 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
* Add information about device encrypted storage status
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
private suspend fun getDeviceEncryptionStatus(): Pair<String, String>? {
|
||||
private fun getDeviceEncryptionStatus(): ItemValue? {
|
||||
return try {
|
||||
val statusText = when (devicePolicyManager.storageEncryptionStatus) {
|
||||
DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED -> ENCRYPTION_STATUS_UNSUPPORTED
|
||||
@@ -106,9 +112,9 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER ->
|
||||
ENCRYPTION_STATUS_ACTIVE_PER_USER
|
||||
|
||||
else -> getString(Res.string.unknown)
|
||||
else -> ENCRYPTION_STATUS_UNKNOWN
|
||||
}
|
||||
getString(Res.string.encrypted_storage) to statusText
|
||||
ItemValue.NameResource(Res.string.encrypted_storage, statusText)
|
||||
} catch (ignored: Exception) {
|
||||
null
|
||||
}
|
||||
@@ -130,7 +136,7 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
val paths = arrayOf(
|
||||
"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su",
|
||||
"/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
|
||||
"/system/bin/failsafe/su", "/data/local/su"
|
||||
"/system/bin/failsafe/su", "/data/local/su",
|
||||
)
|
||||
return paths.any { File(it).exists() }
|
||||
}
|
||||
@@ -163,17 +169,18 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
/**
|
||||
* Get information about security providers
|
||||
*/
|
||||
private suspend fun getSecurityData(): List<Pair<String, String>> {
|
||||
val securityProviders = Security.getProviders().map { Pair(it.name, it.version.toString()) }
|
||||
private fun getSecurityData(): List<ItemValue> {
|
||||
val securityProviders = Security.getProviders()
|
||||
.map { ItemValue.Text(it.name, it.version.toString()) }
|
||||
return buildList {
|
||||
if (securityProviders.isNotEmpty()) {
|
||||
add(getString(Res.string.security_providers) to "")
|
||||
add(ItemValue.NameResource(Res.string.security_providers, ""))
|
||||
addAll(securityProviders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getGsfAndroidId(): Pair<String, String>? {
|
||||
private fun getGsfAndroidId(): ItemValue? {
|
||||
val uri = Uri.parse("content://com.google.android.gsf.gservices")
|
||||
val idKey = "android_id"
|
||||
val params = arrayOf(idKey)
|
||||
@@ -181,26 +188,22 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
contentResolver.query(uri, null, null, params, null)?.use {
|
||||
it.moveToFirst()
|
||||
val hexId = java.lang.Long.toHexString(it.getString(1).toLong())
|
||||
getString(Res.string.google_services_framework_id) to hexId
|
||||
ItemValue.NameResource(Res.string.google_services_framework_id, hexId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getStrongBoxData(): Pair<String, String> {
|
||||
private fun getStrongBoxData(): ItemValue {
|
||||
val hasStrongBox = if (Build.VERSION.SDK_INT >= 28) {
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return getString(Res.string.strongbox) to getYesNoString(hasStrongBox)
|
||||
}
|
||||
|
||||
private suspend fun getYesNoString(value: Boolean) = if (value) {
|
||||
getString(Res.string.yes)
|
||||
} else {
|
||||
getString(Res.string.no)
|
||||
return ItemValue.NameValueResource(
|
||||
Res.string.strongbox, ResourceUtils.getYesNoStringResource(hasStrongBox)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -209,5 +212,6 @@ actual class OsDataProvider actual constructor() : KoinComponent {
|
||||
private const val ENCRYPTION_STATUS_ACTIVATING = "ACTIVATING"
|
||||
private const val ENCRYPTION_STATUS_ACTIVE = "ACTIVE"
|
||||
private const val ENCRYPTION_STATUS_ACTIVE_PER_USER = "ACTIVE_PER_USER"
|
||||
private const val ENCRYPTION_STATUS_UNKNOWN = "UNKNOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import android.content.Context
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class PackageNameProvider actual constructor() : KoinComponent {
|
||||
actual class PackageNameProvider actual constructor() : IPackageNameProvider, KoinComponent {
|
||||
|
||||
private val context: Context by inject()
|
||||
|
||||
actual suspend fun getPackageName(): String {
|
||||
actual override suspend fun getPackageName(): String {
|
||||
return context.packageName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,13 @@ package com.kgurgul.cpuinfo.data.provider
|
||||
import android.os.Build
|
||||
import com.kgurgul.cpuinfo.domain.model.ProcessItem
|
||||
import com.kgurgul.cpuinfo.utils.CpuLogger
|
||||
import org.koin.core.annotation.Factory
|
||||
import java.util.StringTokenizer
|
||||
|
||||
@Factory
|
||||
actual class ProcessesProvider actual constructor() {
|
||||
actual class ProcessesProvider actual constructor() : IProcessesProvider {
|
||||
|
||||
actual fun areProcessesSupported() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.M
|
||||
actual override fun areProcessesSupported() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.M
|
||||
|
||||
actual fun getProcessList(): List<ProcessItem> {
|
||||
actual override fun getProcessList(): List<ProcessItem> {
|
||||
val psCmdList = readPsCmd()
|
||||
return parsePs(psCmdList)
|
||||
}
|
||||
@@ -89,4 +87,4 @@ actual class ProcessesProvider actual constructor() {
|
||||
private const val NICENESS_POSITION = 6
|
||||
private const val NAME_POSITION = 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import android.app.ActivityManager
|
||||
import org.koin.core.annotation.Factory
|
||||
import com.kgurgul.cpuinfo.domain.model.ItemValue
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class RamDataProvider actual constructor() : KoinComponent {
|
||||
actual class RamDataProvider actual constructor() : IRamDataProvider, KoinComponent {
|
||||
|
||||
private val activityManager: ActivityManager by inject()
|
||||
|
||||
actual fun getTotalBytes(): Long {
|
||||
actual override fun getTotalBytes(): Long {
|
||||
val memoryInfo = ActivityManager.MemoryInfo()
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
return memoryInfo.totalMem
|
||||
}
|
||||
|
||||
actual fun getAvailableBytes(): Long {
|
||||
actual override fun getAvailableBytes(): Long {
|
||||
val memoryInfo = ActivityManager.MemoryInfo()
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
return memoryInfo.availMem
|
||||
}
|
||||
|
||||
actual fun getThreshold(): Long {
|
||||
actual override fun getThreshold(): Long {
|
||||
val memoryInfo = ActivityManager.MemoryInfo()
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
return memoryInfo.threshold
|
||||
}
|
||||
|
||||
actual override fun getAdditionalData(): List<ItemValue> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import com.kgurgul.cpuinfo.domain.model.ItemValue
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.absolute_height
|
||||
import com.kgurgul.cpuinfo.shared.absolute_width
|
||||
@@ -16,50 +17,48 @@ import com.kgurgul.cpuinfo.shared.large
|
||||
import com.kgurgul.cpuinfo.shared.normal
|
||||
import com.kgurgul.cpuinfo.shared.refresh_rate
|
||||
import com.kgurgul.cpuinfo.shared.screen_class
|
||||
import com.kgurgul.cpuinfo.shared.screen_density_dpi
|
||||
import com.kgurgul.cpuinfo.shared.small
|
||||
import com.kgurgul.cpuinfo.shared.unknown
|
||||
import com.kgurgul.cpuinfo.shared.width
|
||||
import com.kgurgul.cpuinfo.utils.round2
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class ScreenDataProvider actual constructor() : KoinComponent {
|
||||
actual class ScreenDataProvider actual constructor() : IScreenDataProvider, KoinComponent {
|
||||
|
||||
private val resources: Resources by inject()
|
||||
private val windowManager: WindowManager by inject()
|
||||
|
||||
actual suspend fun getData(): List<Pair<String, String>> {
|
||||
actual override suspend fun getData(): List<ItemValue> {
|
||||
return buildList {
|
||||
add(getScreenClass())
|
||||
add(getDensityClass())
|
||||
addAll(getDensityItems())
|
||||
addAll(getInfoFromDisplayMetrics())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
actual fun getOrientationFlow(): Flow<String> = flow {
|
||||
actual override fun getOrientationFlow(): Flow<String> = flow {
|
||||
val display = windowManager.defaultDisplay
|
||||
emit(display.rotation.toString())
|
||||
}
|
||||
|
||||
private suspend fun getScreenClass(): Pair<String, String> {
|
||||
val screenClass: String = when (
|
||||
private fun getScreenClass(): ItemValue {
|
||||
val screenClass = when (
|
||||
resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
|
||||
) {
|
||||
Configuration.SCREENLAYOUT_SIZE_LARGE -> getString(Res.string.large)
|
||||
Configuration.SCREENLAYOUT_SIZE_NORMAL -> getString(Res.string.normal)
|
||||
Configuration.SCREENLAYOUT_SIZE_SMALL -> getString(Res.string.small)
|
||||
else -> getString(Res.string.unknown)
|
||||
Configuration.SCREENLAYOUT_SIZE_LARGE -> Res.string.large
|
||||
Configuration.SCREENLAYOUT_SIZE_NORMAL -> Res.string.normal
|
||||
Configuration.SCREENLAYOUT_SIZE_SMALL -> Res.string.small
|
||||
else -> Res.string.unknown
|
||||
}
|
||||
return Pair(getString(Res.string.screen_class), screenClass)
|
||||
return ItemValue.NameValueResource(Res.string.screen_class, screenClass)
|
||||
}
|
||||
|
||||
private suspend fun getDensityClass(): Pair<String, String> {
|
||||
private fun getDensityItems(): List<ItemValue> {
|
||||
val densityDpi = resources.displayMetrics.densityDpi
|
||||
val densityClass: String
|
||||
when (densityDpi) {
|
||||
@@ -80,7 +79,8 @@ actual class ScreenDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
|
||||
DisplayMetrics.DENSITY_XXHIGH, DisplayMetrics.DENSITY_360, DisplayMetrics.DENSITY_400,
|
||||
DisplayMetrics.DENSITY_420 -> {
|
||||
DisplayMetrics.DENSITY_420,
|
||||
-> {
|
||||
densityClass = "xxhdpi"
|
||||
}
|
||||
|
||||
@@ -89,69 +89,72 @@ actual class ScreenDataProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
|
||||
else -> {
|
||||
densityClass = getString(Res.string.unknown)
|
||||
densityClass = ""
|
||||
}
|
||||
}
|
||||
return Pair(getString(Res.string.density_class), densityClass)
|
||||
return listOf(
|
||||
ItemValue.NameResource(Res.string.density_class, densityClass),
|
||||
ItemValue.NameResource(Res.string.screen_density_dpi, "$densityDpi dpi"),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private suspend fun getInfoFromDisplayMetrics(): List<Pair<String, String>> {
|
||||
val functionsList = mutableListOf<Pair<String, String>>()
|
||||
private fun getInfoFromDisplayMetrics(): List<ItemValue> {
|
||||
val functionsList = mutableListOf<ItemValue>()
|
||||
val display = windowManager.defaultDisplay
|
||||
val metrics = DisplayMetrics()
|
||||
try {
|
||||
display.getRealMetrics(metrics)
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.width),
|
||||
"${metrics.widthPixels}px"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.width,
|
||||
"${metrics.widthPixels} px",
|
||||
),
|
||||
)
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.height),
|
||||
"${metrics.heightPixels}px"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.height,
|
||||
"${metrics.heightPixels} px",
|
||||
),
|
||||
)
|
||||
|
||||
val density = metrics.density
|
||||
val dpHeight = metrics.heightPixels / density
|
||||
val dpWidth = metrics.widthPixels / density
|
||||
functionsList.add(Pair(getString(Res.string.dp_width), "${dpWidth.toInt()}dp"))
|
||||
functionsList.add(ItemValue.NameResource(Res.string.dp_width, "${dpWidth.toInt()} dp"))
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.dp_height),
|
||||
"${dpHeight.toInt()}dp"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.dp_height,
|
||||
"${dpHeight.toInt()} dp",
|
||||
),
|
||||
)
|
||||
functionsList.add(Pair(getString(Res.string.density), "$density"))
|
||||
} catch (e: Exception) {
|
||||
functionsList.add(ItemValue.NameResource(Res.string.density, "$density"))
|
||||
} catch (_: Exception) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
display.getMetrics(metrics)
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.absolute_width),
|
||||
"${metrics.widthPixels}px"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.absolute_width,
|
||||
"${metrics.widthPixels} px",
|
||||
),
|
||||
)
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.absolute_height),
|
||||
"${metrics.heightPixels}px"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.absolute_height,
|
||||
"${metrics.heightPixels} px",
|
||||
),
|
||||
)
|
||||
|
||||
val refreshRate = display.refreshRate
|
||||
functionsList.add(
|
||||
Pair(
|
||||
getString(Res.string.refresh_rate),
|
||||
"${refreshRate.round2()}"
|
||||
)
|
||||
ItemValue.NameResource(
|
||||
Res.string.refresh_rate,
|
||||
"${refreshRate.round2()} Hz",
|
||||
),
|
||||
)
|
||||
|
||||
return functionsList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,16 @@ import android.hardware.SensorManager
|
||||
import android.hardware.TriggerEvent
|
||||
import android.hardware.TriggerEventListener
|
||||
import com.kgurgul.cpuinfo.domain.model.SensorData
|
||||
import com.kgurgul.cpuinfo.domain.model.TextResource
|
||||
import com.kgurgul.cpuinfo.domain.observable.TemperatureDataObservable.Companion.GOOGLE_GYRO_TEMPERATURE_SENSOR_TYPE
|
||||
import com.kgurgul.cpuinfo.domain.observable.TemperatureDataObservable.Companion.GOOGLE_PRESSURE_TEMPERATURE_SENSOR_TYPE
|
||||
import com.kgurgul.cpuinfo.utils.round1
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@Factory
|
||||
actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
|
||||
private val sensorManager: SensorManager by inject()
|
||||
@@ -27,7 +26,7 @@ actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
val initialData = sensorList.map {
|
||||
SensorData(
|
||||
id = it.getUniqueId(),
|
||||
name = it.name,
|
||||
name = TextResource.Text(it.name),
|
||||
value = " ",
|
||||
)
|
||||
}
|
||||
@@ -37,9 +36,9 @@ actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
val updatedSensor = listOf(
|
||||
SensorData(
|
||||
id = event.sensor.getUniqueId(),
|
||||
name = event.sensor.name,
|
||||
value = getSensorData(event.sensor.type, event.values)
|
||||
)
|
||||
name = TextResource.Text(event.sensor.name),
|
||||
value = getSensorData(event.sensor.type, event.values),
|
||||
),
|
||||
)
|
||||
trySend(updatedSensor)
|
||||
}
|
||||
@@ -56,20 +55,20 @@ actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
val updatedSensor = listOf(
|
||||
SensorData(
|
||||
id = event.sensor.getUniqueId(),
|
||||
name = event.sensor.name,
|
||||
value = getSensorData(event.sensor.type, event.values)
|
||||
)
|
||||
name = TextResource.Text(event.sensor.name),
|
||||
value = getSensorData(event.sensor.type, event.values),
|
||||
),
|
||||
)
|
||||
trySend(updatedSensor)
|
||||
}
|
||||
},
|
||||
sensor
|
||||
sensor,
|
||||
)
|
||||
} else {
|
||||
sensorManager.registerListener(
|
||||
sensorListener,
|
||||
sensor,
|
||||
SensorManager.SENSOR_DELAY_NORMAL
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +117,8 @@ actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
|
||||
Sensor.TYPE_AMBIENT_TEMPERATURE,
|
||||
GOOGLE_GYRO_TEMPERATURE_SENSOR_TYPE,
|
||||
GOOGLE_PRESSURE_TEMPERATURE_SENSOR_TYPE ->
|
||||
GOOGLE_PRESSURE_TEMPERATURE_SENSOR_TYPE,
|
||||
->
|
||||
data = "Temperature=${values[0].round1()}°C"
|
||||
|
||||
Sensor.TYPE_LIGHT ->
|
||||
@@ -172,4 +172,4 @@ actual class SensorsInfoProvider actual constructor() : KoinComponent {
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.kgurgul.cpuinfo.data.provider
|
||||
|
||||
import android.os.Environment
|
||||
import com.kgurgul.cpuinfo.domain.model.StorageItem
|
||||
import com.kgurgul.cpuinfo.domain.model.TextResource
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.baseline_folder_24
|
||||
import com.kgurgul.cpuinfo.shared.baseline_folder_special_24
|
||||
@@ -9,19 +10,15 @@ import com.kgurgul.cpuinfo.shared.baseline_sd_storage_24
|
||||
import com.kgurgul.cpuinfo.shared.external
|
||||
import com.kgurgul.cpuinfo.shared.internal
|
||||
import com.kgurgul.cpuinfo.utils.CpuLogger
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Factory
|
||||
import java.io.BufferedReader
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
@Factory
|
||||
actual class StorageDataProvider actual constructor() {
|
||||
actual class StorageDataProvider actual constructor() : IStorageDataProvider {
|
||||
|
||||
actual fun getStorageInfo(): List<StorageItem> {
|
||||
actual override suspend fun getStorageInfo(): List<StorageItem> {
|
||||
return listOfNotNull(getInternalStorage(), getExternalStorage(), getSdCardStorage())
|
||||
}
|
||||
|
||||
@@ -33,7 +30,7 @@ actual class StorageDataProvider actual constructor() {
|
||||
return if (storageTotal > 0) {
|
||||
StorageItem(
|
||||
id = "0",
|
||||
label = runBlocking { getString(Res.string.internal) + ": " },
|
||||
label = TextResource.Resource(Res.string.internal),
|
||||
iconDrawable = Res.drawable.baseline_folder_special_24,
|
||||
storageTotal = storageTotal,
|
||||
storageUsed = storageUsed,
|
||||
@@ -52,7 +49,7 @@ actual class StorageDataProvider actual constructor() {
|
||||
if (storageTotal > 0) {
|
||||
StorageItem(
|
||||
id = "1",
|
||||
label = runBlocking { getString(Res.string.external) + ": " },
|
||||
label = TextResource.Resource(Res.string.external),
|
||||
iconDrawable = Res.drawable.baseline_folder_24,
|
||||
storageTotal = storageTotal,
|
||||
storageUsed = storageUsed,
|
||||
@@ -81,7 +78,7 @@ actual class StorageDataProvider actual constructor() {
|
||||
return if (storageTotal > 0) {
|
||||
StorageItem(
|
||||
id = "2",
|
||||
label = runBlocking { getString(Res.string.external) + ": " },
|
||||
label = TextResource.Resource(Res.string.external),
|
||||
iconDrawable = Res.drawable.baseline_sd_storage_24,
|
||||
storageTotal = storageTotal,
|
||||
storageUsed = storageUsed,
|
||||
@@ -109,33 +106,35 @@ actual class StorageDataProvider actual constructor() {
|
||||
val strLine = br.readLine()
|
||||
if (strLine == null) {
|
||||
break
|
||||
} else if (!(strLine.contains("asec")
|
||||
|| strLine.contains("legacy")
|
||||
|| strLine.contains("Android/obb"))
|
||||
} else if (!(
|
||||
strLine.contains("asec") ||
|
||||
strLine.contains("legacy") ||
|
||||
strLine.contains("Android/obb")
|
||||
)
|
||||
) {
|
||||
if (strLine.startsWith("/dev/block/vold/")
|
||||
|| strLine.startsWith("/dev/block/sd")
|
||||
|| strLine.startsWith("/dev/fuse")
|
||||
|| strLine.startsWith("/mnt/media_rw")
|
||||
if (strLine.startsWith("/dev/block/vold/") ||
|
||||
strLine.startsWith("/dev/block/sd") ||
|
||||
strLine.startsWith("/dev/fuse") ||
|
||||
strLine.startsWith("/mnt/media_rw")
|
||||
) {
|
||||
val lineElements = strLine
|
||||
.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
val path = File(lineElements[1])
|
||||
if ((path.exists() || path.isDirectory || path.canWrite())
|
||||
&& path.exists()
|
||||
//&& path.canRead()
|
||||
&& !path.path.contains("/system")
|
||||
&& !sdDirList.contains(lineElements[1])
|
||||
&& lineElements[1] != externalDir
|
||||
&& lineElements[1] != "/storage/emulated"
|
||||
&& !sdDirList.any {
|
||||
if ((path.exists() || path.isDirectory || path.canWrite()) &&
|
||||
path.exists() &&
|
||||
// && path.canRead()
|
||||
!path.path.contains("/system") &&
|
||||
!sdDirList.contains(lineElements[1]) &&
|
||||
lineElements[1] != externalDir &&
|
||||
lineElements[1] != "/storage/emulated" &&
|
||||
!sdDirList.any {
|
||||
it.endsWith(
|
||||
lineElements[1]
|
||||
.substring(
|
||||
lineElements[1].lastIndexOf("/"),
|
||||
lineElements[1].length
|
||||
)
|
||||
lineElements[1].length,
|
||||
),
|
||||
)
|
||||
}
|
||||
) {
|
||||
@@ -150,4 +149,4 @@ actual class StorageDataProvider actual constructor() {
|
||||
}
|
||||
return sdDirList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,36 +25,40 @@ import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.BatteryManager
|
||||
import com.kgurgul.cpuinfo.domain.model.TemperatureItem
|
||||
import com.kgurgul.cpuinfo.domain.model.TextResource
|
||||
import com.kgurgul.cpuinfo.domain.observable.TemperatureDataObservable.Companion.GOOGLE_GYRO_TEMPERATURE_SENSOR_TYPE
|
||||
import com.kgurgul.cpuinfo.domain.observable.TemperatureDataObservable.Companion.GOOGLE_PRESSURE_TEMPERATURE_SENSOR_TYPE
|
||||
import com.kgurgul.cpuinfo.shared.Res
|
||||
import com.kgurgul.cpuinfo.shared.baseline_thermostat_24
|
||||
import com.kgurgul.cpuinfo.utils.round1
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.koin.core.annotation.Factory
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
|
||||
@Factory
|
||||
actual class TemperatureProvider actual constructor() : KoinComponent {
|
||||
actual class TemperatureProvider actual constructor() :
|
||||
KoinComponent,
|
||||
ITemperatureProvider {
|
||||
|
||||
private val appContext: Context by inject()
|
||||
private val sensorManager: SensorManager by inject()
|
||||
|
||||
actual val sensorsFlow = callbackFlow {
|
||||
actual override val sensorsFlow = callbackFlow {
|
||||
val sensorCallback = object : SensorEventListener {
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
trySendBlocking(
|
||||
TemperatureItem(
|
||||
id = event.sensor.type,
|
||||
icon = Res.drawable.baseline_thermostat_24,
|
||||
name = event.sensor.name,
|
||||
temperature = event.values[0].round1()
|
||||
val temp = event.values[0].round1()
|
||||
if (isTemperatureValid(temp.toDouble())) {
|
||||
trySendBlocking(
|
||||
TemperatureItem(
|
||||
id = event.sensor.type,
|
||||
icon = Res.drawable.baseline_thermostat_24,
|
||||
name = TextResource.Text(event.sensor.name),
|
||||
temperature = temp,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
|
||||
@@ -65,20 +69,20 @@ actual class TemperatureProvider actual constructor() : KoinComponent {
|
||||
sensorManager.registerListener(
|
||||
sensorCallback,
|
||||
it,
|
||||
SensorManager.SENSOR_DELAY_NORMAL
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
)
|
||||
}
|
||||
awaitClose { sensorManager.unregisterListener(sensorCallback) }
|
||||
}
|
||||
|
||||
actual fun getBatteryTemperature(): Float? {
|
||||
actual override fun getBatteryTemperature(): Float? {
|
||||
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||
val batteryStatus = appContext.registerReceiver(null, filter)
|
||||
val temp = batteryStatus?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, Int.MIN_VALUE)
|
||||
return if (temp != null && temp != Int.MIN_VALUE) temp / 10f else null
|
||||
}
|
||||
|
||||
actual fun findCpuTemperatureLocation(): String? {
|
||||
actual override fun findCpuTemperatureLocation(): String? {
|
||||
for (location in CPU_TEMP_FILE_PATHS) {
|
||||
try {
|
||||
val temp = File(location).bufferedReader().use { it.readLine().toDoubleOrNull() }
|
||||
@@ -98,7 +102,7 @@ actual class TemperatureProvider actual constructor() : KoinComponent {
|
||||
*
|
||||
* @return CPU temperature
|
||||
*/
|
||||
actual fun getCpuTemp(path: String): Float? {
|
||||
actual override fun getCpuTemperature(path: String): Float? {
|
||||
return File(path).bufferedReader().use { it.readLine().toDoubleOrNull() }?.let {
|
||||
if (isTemperatureValid(it)) {
|
||||
it.toFloat()
|
||||
@@ -108,12 +112,7 @@ actual class TemperatureProvider actual constructor() : KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed temperature is in normal range: -30 - 250 Celsius
|
||||
*
|
||||
* @param temp current temperature
|
||||
*/
|
||||
private fun isTemperatureValid(temp: Double): Boolean = temp in -30.0..250.0
|
||||
private fun isTemperatureValid(temp: Double): Boolean = temp in -50.0..250.0
|
||||
|
||||
companion object {
|
||||
// Ugly but currently the easiest working solution is to search well known locations
|
||||
@@ -147,4 +146,4 @@ actual class TemperatureProvider actual constructor() : KoinComponent {
|
||||
GOOGLE_PRESSURE_TEMPERATURE_SENSOR_TYPE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||