Compare commits

...
404 Commits
Author SHA1 Message Date
Kamil Gurgul 5808646932 [WEAR] Adjust proguard 2025-04-24 07:16:43 +02:00
Kamil Gurgul d0173e0b8c [WEAR] Fix brand icon 2025-04-24 07:12:48 +02:00
Kamil Gurgul d22f8477bb [SHARED] Update libs 2025-04-24 06:50:30 +02:00
Kamil Gurgul 05bd0a125a [#312] Add density DPI 2025-04-22 08:24:48 +02:00
Kamil Gurgul ebecc5af19 [SHARED] Update libs 2025-04-22 08:14:48 +02:00
Kamil Gurgul 7ed8caa7df [SHARED] Update libs 2025-04-16 06:26:48 +02:00
Kamil Gurgul 4ff7a02cf4 [SHARED] Update libs 2025-04-11 11:30:39 +02:00
Kamil Gurgul 646771b573 [SHARED] Update libs 2025-04-10 06:50:50 +02:00
Kamil GurgulandGitHub b1d01b49da Update libs.versions.toml 2025-04-09 08:58:58 +02:00
Kamil GurgulandGitHub dbc059ac33 Update libs.versions.toml 2025-04-09 08:57:20 +02:00
Kamil Gurgul 8fd00c61f9 [SHARED] Add licenses plugin 2025-04-03 10:14:10 +02:00
Kamil Gurgul 22f8535000 [SHARED] Update licenses 2025-04-03 10:01:55 +02:00
Kamil Gurgul 5f1e4cb34d [SHARED] Update libs 2025-04-02 06:23:40 +02:00
Kamil Gurgul 544cb3c9c7 [SHARED] Fix unused imports 2025-04-01 06:58:49 +02:00
Kamil Gurgul 199d05b32d [SHARED] Fix typo 2025-04-01 06:55:47 +02:00
Kamil Gurgul e8ebde087d [SHARED] Update libs 2025-04-01 06:30:06 +02:00
Kamil Gurgul 0d1a238dc3 [ANDROID] Remove unnecessary R8 rules 2025-03-31 07:00:35 +02:00
Kamil Gurgul fe98599f6e [SHARED] Refresh icon always visible for desktop 2025-03-31 06:54:07 +02:00
Kamil Gurgul 914ca80d4c [SHARED] Update actions 2025-03-28 05:53:39 +01:00
Kamil Gurgul 38ecc6c5ed [SHARED] Update libs 2025-03-27 06:19:22 +01:00
Kamil Gurgul c042226428 [SHARED] Update libs 2025-03-27 06:17:03 +01:00
Kamil Gurgul 40275ce377 [SHARED] Add applications manual refresh for desktop 2025-03-26 07:07:25 +01:00
Kamil Gurgul 7d01bf4a8f [SHARED] Update koin 2025-03-26 06:52:16 +01:00
Kamil Gurgul 62800e0d14 [JVM] Enable application list for desktop 2025-03-25 09:46:40 +01:00
Kamil Gurgul 51b3427289 [ANDROID] Update AGP 2025-03-25 07:36:18 +01:00
Kamil Gurgul aeb5a16333 [DESKTOP] Use OSHI for OS app list on JVM 2025-03-24 06:51:27 +01:00
Kamil Gurgul f0871041e9 [SHARED] Update actions 2025-03-24 06:40:44 +01:00
Kamil Gurgul e67f8e1cca [SHARED] Update libs 2025-03-23 06:04:30 +01:00
Kamil Gurgul bef0e36ecb [IOS] Update actions 2025-03-21 07:44:39 +01:00
Kamil Gurgul ef2fa5b3cc [SHARED] Update kotlin 2025-03-21 07:43:17 +01:00
Kamil Gurgul e3bee07ec4 [SHARED] Update about button 2025-03-17 07:20:38 +01:00
Kamil Gurgul 1455be36a2 [SHARED] Update libs 2025-03-13 07:00:21 +01:00
Kamil Gurgul b57bf89f3c [ANDROID] Bump version 2025-03-08 09:22:45 +01:00
Kamil Gurgul 106a9daa1c [ANDROID] Downgrade version 2025-03-08 07:16:07 +01:00
Kamil Gurgul 3d7726e93a [ANDROID] Update AGP 2025-03-05 06:32:55 +01:00
Kamil Gurgul b1202af131 [SHARED] Update licenses 2025-03-04 08:08:32 +01:00
Kamil Gurgul b1dc0ec4a5 [DESKTOP] Bump version 2025-03-03 07:20:03 +01:00
Kamil Gurgul bd5396dec3 [DESKTOP] Bump version 2025-03-01 08:47:06 +01:00
Kamil Gurgul 7de9dced00 [SHARED] Update libs 2025-02-28 07:32:00 +01:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
38990629e1 Update dependency gradle to v8.13 (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:50:54 +01:00
Kamil Gurgul 6ca814c3d9 [SHARED] Update libs 2025-02-27 07:45:32 +01:00
Kamil Gurgul 841085c585 [JVM] Update proguard rules - serialization doesn't work 2025-02-25 07:44:42 +01:00
Kamil Gurgul 5dc70e48af [WASM] Bring back yarn 2025-02-25 06:48:55 +01:00
Kamil Gurgul fbb1141e46 [WASM] Update package-lock.json 2025-02-24 08:28:25 +01:00
Kamil Gurgul 7b757600a8 [SHARED] Fix settings scrollbar 2025-02-24 08:24:53 +01:00
Kamil Gurgul e4435e3121 [WASM] Try to update npm deps 2025-02-24 08:21:39 +01:00
Kamil Gurgul 576b698cd6 [WASM] Disable yarn 2025-02-24 08:16:03 +01:00
Kamil Gurgul c03ed8027e [ANDROID] Bump version 2025-02-22 07:00:05 +01:00
Kamil Gurgul 6dbb4970dc [ANDROID] Bump version 2025-02-22 06:41:59 +01:00
Kamil Gurgul d1383116b7 [SHARED] Update processes and apps unit tests 2025-02-21 07:20:03 +01:00
Kamil Gurgul 62528cbd46 [SHARED] Add processes filtering unit tests 2025-02-21 07:11:35 +01:00
Kamil Gurgul a1fee26f98 [SHARED] Add apps filtering unit tests 2025-02-21 07:04:44 +01:00
Kamil Gurgul 7f6056ecc2 [SHARED] Add nested licenses deeplink 2025-02-20 08:48:26 +01:00
Kamil Gurgul 4706196f95 [TV] Fix deep links navigation 2025-02-19 09:26:53 +01:00
Kamil Gurgul a4fde9154f [SHARED] Fix deep links navigation 2025-02-19 09:20:24 +01:00
Kamil Gurgul 5474536802 [SHARED] Fix processes item animation 2025-02-19 08:21:30 +01:00
Kamil Gurgul 73c71e5c4b [SHARED] Add processes search, adjust apps search UI 2025-02-18 15:08:02 +01:00
Kamil Gurgul 3e57ad3f75 [SHARED] Base for processes search 2025-02-18 13:09:46 +01:00
Kamil Gurgul efde9ea8f2 [SHARED] Format readme, add .aiexclude 2025-02-18 08:01:56 +01:00
Kamil Gurgul 0cf7a53a2f [WEAR] Add apps version name 2025-02-17 07:26:32 +01:00
Kamil Gurgul 80b33a8058 [TV] Add apps version name 2025-02-17 07:19:05 +01:00
Kamil Gurgul 8430b28fda [TV] Adjust search on applications 2025-02-17 06:52:54 +01:00
Kamil Gurgul 6292cabbb7 [TV] Add search for TV 2025-02-15 09:45:47 +01:00
Kamil Gurgul db804107ef [SHARED] Fix missing content description 2025-02-15 07:37:04 +01:00
Kamil Gurgul 214f541d02 [SHARED] Fix license uri 2025-02-14 10:28:49 +01:00
Kamil Gurgul 2a04e43d51 [SHARED] Update libs 2025-02-14 10:27:51 +01:00
Kamil Gurgul 1d93e439ce [SHARED] Add uri opening safe handler 2025-02-14 07:42:33 +01:00
Kamil Gurgul be4a62470e [SHARED] Update libs 2025-02-13 09:12:40 +01:00
Kamil Gurgul b2c3c365ff [SHARED] Add version name to apps list 2025-02-13 09:09:16 +01:00
Kamil Gurgul e605090dfe [ANDROID] Change app data provider 2025-02-12 15:24:52 +01:00
Kamil Gurgul 0addf44aa5 [SHARED] Add applications search 2025-02-12 10:47:56 +01:00
Kamil Gurgul ed7c41589c [SHARED] Fix unit tests 2025-02-11 09:01:24 +01:00
Kamil Gurgul bd2ee36aa6 [SHARED] Initial application search data flow 2025-02-11 08:54:38 +01:00
Kamil Gurgul a596520964 [SHARED] Base for application search 2025-02-11 07:25:45 +01:00
Kamil Gurgul 84cad99f20 [SHARED] Fix unit tests 2025-02-10 08:22:08 +01:00
Kamil Gurgul 303b721c8d [SHARED][#97] Add about section 2025-02-10 08:09:41 +01:00
Kamil Gurgul ca74962010 [ANDROID] Fix unit tests 2025-02-06 09:19:25 +01:00
Kamil Gurgul ef6f687dce [ANDROID] Add shortcuts 2025-02-06 09:01:47 +01:00
Kamil Gurgul 20d2d868bc [ANDROID] Deep links refactor 2025-02-05 08:16:23 +01:00
Kamil Gurgul 51d4ba170c [SHARED] Update libs 2025-02-05 07:10:34 +01:00
Kamil Gurgul 4b5b4b646b [ANDROID] Add deep links 2025-02-05 07:06:22 +01:00
Kamil Gurgul 45e3c0e2db [ANDROID] Fix TV nav 2025-02-04 10:03:40 +01:00
Kamil Gurgul 4224e564a5 [ANDROID] Update navigation models 2025-02-04 09:56:04 +01:00
Kamil Gurgul b6a80a6bc6 [ANDROID] Add base deeplink 2025-02-04 09:22:06 +01:00
Kamil Gurgul 565fe66958 [ANDROID] Refactor tile service 2025-02-04 07:35:29 +01:00
Kamil Gurgul 00c91abbf0 [TV] Refactor NavHost animations 2025-02-04 07:29:53 +01:00
Kamil Gurgul fd3672b40c [WEAR] Fix actions formatting 2025-02-01 13:44:26 +01:00
Kamil Gurgul 41e863f0bc [SHARED] Update libs, fix applications layout 2025-01-30 08:48:58 +01:00
Kamil Gurgul 29d9ab78be [SHARED] Update kotlin 2025-01-29 06:43:49 +01:00
Kamil Gurgul 02ecd6e86b [SHARED] Adjust ram screen dividers 2025-01-27 08:47:06 +01:00
Kamil Gurgul 99cc479e09 [SHARED] Add desktop ram additional info 2025-01-27 08:19:16 +01:00
Kamil Gurgul bc6908b453 [SHARED] Update libs 2025-01-26 09:21:23 +01:00
Kamil Gurgul b75a2508da [SHARED] Update gradle 2025-01-25 09:48:58 +01:00
Kamil Gurgul 14655fa683 [SHARED] Update readme 2025-01-25 09:38:52 +01:00
Kamil Gurgul 93b43a58db [SHARED] Add licenses interactor tests 2025-01-24 07:51:58 +01:00
Kamil Gurgul b7b8553c23 [SHARED] Add fake licenses provider 2025-01-24 07:40:34 +01:00
Kamil Gurgul d274f16b12 [SHARED] Handle licenses webpage 2025-01-24 06:59:55 +01:00
Kamil Gurgul e9305be96f [SHARED] Fix preview 2025-01-23 09:41:48 +01:00
Kamil Gurgul 551d706b7f [SHARED] Add licenses screen 2025-01-23 09:35:03 +01:00
Kamil Gurgul 6368bc35ef [SHARED] Update dependency plugin 2025-01-23 08:57:14 +01:00
Kamil Gurgul ef44f0e9f7 [SHARED] Simplified license screen added 2025-01-23 08:53:46 +01:00
Kamil Gurgul 029b24969c [SHARED] Add licenses provider 2025-01-22 10:23:53 +01:00
Kamil Gurgul 3c5ead03e2 [SHARED] Add serialization library 2025-01-22 08:39:25 +01:00
Kamil Gurgul 85a5f2042d [SHARED] Update koin 2025-01-22 07:48:45 +01:00
Kamil Gurgul 35018cdad2 [SHARED] Add base for licenses parsing 2025-01-21 11:52:33 +01:00
Kamil Gurgul 94adebd220 [SHARED] Add licenses copy 2025-01-21 11:40:34 +01:00
Kamil Gurgul 7b365afdcd [SHARED] Update licenses file 2025-01-21 11:32:15 +01:00
Kamil Gurgul 9391fdc868 [SHARED] Update navigation lib 2025-01-21 11:14:50 +01:00
Kamil Gurgul f8c3aa1172 [SHARED] Simplify settings VM 2025-01-20 08:24:28 +01:00
Kamil Gurgul 9550541aaa [ANDROID] Replace AppCompatActivity with ComponentActivity 2025-01-20 07:01:06 +01:00
Kamil Gurgul bf0785148e [ANDROID] Libs cleanup 2025-01-20 06:55:17 +01:00
Kamil Gurgul cfd5a23b3d [ANDROID] Update navigation library 2025-01-20 06:37:37 +01:00
Kamil Gurgul f6e4f0ab78 [SHARED] Add base for licenses 2025-01-19 09:50:43 +01:00
Kamil Gurgul e30d5b02b4 [SHARED] Add homebrew to readme 2025-01-18 09:54:45 +01:00
Kamil Gurgul 7c62c9baf1 [SHARED] Add homebrew custom badge 2025-01-18 09:53:15 +01:00
Kamil Gurgul c037dfb533 [SHARED] Add licenses file 2025-01-17 08:50:34 +01:00
Kamil Gurgul 15dc794093 [SHARED] Update readme 2025-01-17 08:08:03 +01:00
Kamil Gurgul 0c6884481e [DESKTOP] Fix release script 2025-01-17 07:17:45 +01:00
Kamil Gurgul 0e0128b8b2 [SHARED] Fix release build with DataStore 2025-01-16 11:12:45 +01:00
Kamil Gurgul 669108744d [DESKTOP] Fix desktop release action 2025-01-16 07:32:02 +01:00
Kamil Gurgul 18cb5845a6 [DESKTOP] Fix desktop release action 2025-01-16 07:25:14 +01:00
Kamil Gurgul 728a068a7b [SHARED] Update libs 2025-01-16 07:22:19 +01:00
Kamil Gurgul e6dd86719f [DESKTOP] Add Mac x64 build lane 2025-01-16 07:13:01 +01:00
Kamil Gurgul 8f932de25d [DESKTOP] Add Mac x64 build lane 2025-01-16 07:12:10 +01:00
Kamil Gurgul 93c9408419 [DESKTOP] Add Mac Uber JAR 2025-01-15 13:16:26 +01:00
Kamil Gurgul a567936a7a [DESKTOP] Add Mac Uber JAR 2025-01-15 13:12:56 +01:00
Kamil Gurgul ce3e7f7f81 [DESKTOP] Try to build multiplatform uber jar 2025-01-15 10:22:49 +01:00
Kamil Gurgul 6aace451df [DESKTOP] Bump desktop version 2025-01-15 07:50:06 +01:00
Kamil Gurgul 323859bed2 [DESKTOP] Change app settings file location 2025-01-15 07:08:13 +01:00
Kamil Gurgul c1e199c188 [DESKTOP] Change app settings file location 2025-01-14 12:07:52 +01:00
Kamil Gurgul 817a027c55 [SHARED] Update readme 2025-01-14 08:07:16 +01:00
Kamil Gurgul 73db5b0a9e [SHARED] Add initial shared UI test 2025-01-14 08:06:01 +01:00
Kamil Gurgul 73ab8213fd [SHARED] Add github templates 2025-01-13 08:55:19 +01:00
Kamil Gurgul 7789e20408 [DESKTOP] Extract app name to strings 2025-01-13 08:30:10 +01:00
Kamil Gurgul 4aceb4244e [SHARED] Update ui tests config 2025-01-13 08:12:51 +01:00
Kamil Gurgul 843be6f760 [SHARED] Update ui tests config 2025-01-13 07:10:32 +01:00
Kamil Gurgul 3505e1ecf8 [SHARED] Update readme 2025-01-12 13:19:57 +01:00
Kamil Gurgul fc7986b654 [SHARED] Update readme 2025-01-12 12:32:52 +01:00
Kamil Gurgul 258f61705a [ANDROID] Update AGP 2025-01-10 07:43:11 +01:00
Kamil Gurgul 2e89f36b8f [SHARED] Update readme 2025-01-10 07:38:34 +01:00
Kamil Gurgul e288629ccb [SHARED] Update readme 2025-01-10 07:26:30 +01:00
Kamil Gurgul 47a4acc6b7 [WEAR] Update kover and readme 2025-01-09 08:34:46 +01:00
Kamil Gurgul 39eb2a9c01 [WEAR] Update release script 2025-01-09 08:16:10 +01:00
Kamil Gurgul 3582d6dd0a [WEAR] Update release script 2025-01-09 08:15:05 +01:00
Kamil Gurgul d053c1f79b [WEAR] Update release script 2025-01-09 08:12:29 +01:00
Kamil Gurgul bd375abe5c [WEAR] Update readme 2025-01-09 07:40:06 +01:00
Kamil Gurgul 1c48b27046 [WEAR] Bring back common app ID 2025-01-08 10:42:42 +01:00
Kamil Gurgul 3281f13fd1 [WEAR] Add VM sharing logic and temp unit switcher 2025-01-08 10:41:00 +01:00
Kamil Gurgul af61146925 [WEAR] Add nested navigation for settings 2025-01-08 09:11:39 +01:00
Kamil Gurgul c423bee933 [WEAR] Base for settings screen 2025-01-08 07:45:03 +01:00
Kamil Gurgul efe9bd68e7 [WEAR] Add settings for apps 2025-01-07 09:41:23 +01:00
Kamil Gurgul fe15e06da7 [WEAR] Add refresh for apps 2025-01-07 09:05:09 +01:00
Kamil Gurgul aadaa2637e [WEAR] Add confirmation for apps 2025-01-07 08:36:22 +01:00
Kamil Gurgul 86fe039a99 [WEAR] Add progress for apps 2025-01-07 08:33:28 +01:00
Kamil Gurgul 5a414331fb [WEAR] Add temperature screen 2025-01-06 13:27:29 +01:00
Kamil Gurgul 0093dcb697 [WEAR] Add temperature screen 2025-01-06 13:27:07 +01:00
Kamil Gurgul 7d8e04f3d3 [WEAR] Add click handler for apps 2025-01-05 10:15:54 +01:00
Kamil Gurgul dbad949d28 [WEAR] Add missing modifiers 2025-01-05 10:09:24 +01:00
Kamil Gurgul d5b90d53f6 [WEAR] Fix reveal action 2025-01-05 09:58:05 +01:00
Kamil Gurgul f5fae0ca73 [WEAR] Base for applications 2025-01-04 14:39:46 +01:00
Kamil Gurgul 8ce329afeb [WEAR] Base for applications 2025-01-04 10:11:59 +01:00
Kamil Gurgul e958c342ea [WEAR] Add sensors screen 2025-01-03 09:07:01 +01:00
Kamil Gurgul cb281783a5 [WEAR] Add hardware screen 2025-01-03 09:03:25 +01:00
Kamil Gurgul 5a3dd7eb57 [WEAR] Add os and screen info screen 2025-01-03 08:49:09 +01:00
Kamil Gurgul b8c91011e5 [WEAR] Add storage screen 2025-01-03 06:43:23 +01:00
Kamil Gurgul acdc328cdc [WEAR] Add RAM screen 2025-01-02 09:04:52 +01:00
Kamil Gurgul 9a055c829b [WEAR] Add GPU screen 2025-01-02 08:42:46 +01:00
Kamil Gurgul 736fb18f34 [WEAR] Fix scroll position on menu 2025-01-02 07:51:45 +01:00
Kamil Gurgul 6887c67642 [WEAR] Add CPU screen 2025-01-02 07:49:16 +01:00
Kamil Gurgul bc571c1f42 [WEAR] Base for info screens 2025-01-01 17:31:18 +01:00
Kamil Gurgul bb6efbb43d [WEAR] Add navigation config 2025-01-01 16:56:57 +01:00
Kamil Gurgul 1e495c483f [WEAR] Fix app background 2025-01-01 16:51:02 +01:00
Kamil Gurgul 62525a6075 [WEAR] Add host UiState to wear menu 2025-01-01 16:48:12 +01:00
Kamil Gurgul 508fbb3679 [WEAR] Base for information screen 2024-12-31 09:58:06 +01:00
Kamil Gurgul bac586faf7 [WEAR] Add menu entries 2024-12-31 09:54:16 +01:00
Kamil Gurgul 8fe5a7a78e [WEAR] Start building menu 2024-12-31 09:50:15 +01:00
Kamil Gurgul 12d52c0798 [WEAR] Adjust splash screen 2024-12-31 09:18:49 +01:00
Kamil Gurgul d29f100bbe [WEAR] Adjust theme and names 2024-12-30 09:04:21 +01:00
Kamil Gurgul 111908d84c [WEAR] Add initial typography 2024-12-30 09:02:26 +01:00
Kamil Gurgul 6f79f3b232 [WEAR] Add initial colors 2024-12-30 09:02:09 +01:00
Kamil Gurgul 853908d04f [WEAR] Add base for Wear OS 2024-12-29 14:10:51 +01:00
Kamil Gurgul cb13ee6869 [ANDROID] Update gitignore 2024-12-28 09:42:15 +01:00
Kamil Gurgul 81e9d07802 [TV] Package cleanup 2024-12-28 09:35:49 +01:00
Kamil Gurgul 66a40447a3 [SHARED] Update README 2024-12-28 09:17:03 +01:00
Kamil Gurgul 825b2f75ba [TV] Simplify TV activity 2024-12-28 08:09:01 +01:00
Kamil Gurgul 81a6d84126 [SHARED] Update README 2024-12-26 11:30:40 +01:00
Kamil Gurgul 55d0c6905c [SHARED] Update README 2024-12-26 11:27:45 +01:00
Kamil Gurgul 50169b11dd [SHARED] Code cleanup 2024-12-25 09:54:34 +01:00
Kamil Gurgul 718767919e [SHARED] Update gradle 2024-12-25 09:23:15 +01:00
Kamil Gurgul 23c295e82e [SHARED] Update koin 2024-12-24 09:41:13 +01:00
Kamil Gurgul 5e4f8b67fb [SHARED] Bump android version 2024-12-23 14:46:30 +01:00
Kamil Gurgul e77abf814f [SHARED] Fix issue with DraggableBox edge case 2024-12-23 14:28:50 +01:00
Kamil Gurgul 484ec75afd [ANDROID] Bump version 2024-12-22 07:59:07 +01:00
Kamil Gurgul bddeb135a4 [SHARED] Update libs 2024-12-21 09:21:31 +01:00
Kamil Gurgul 459a309dbe [SHARED] Update coroutines 2024-12-20 10:29:32 +01:00
Kamil Gurgul 7bf77f2157 [SHARED] Fix apps VM UiState refreshing 2024-12-19 11:25:21 +01:00
Kamil Gurgul 0a5a9f8a51 [TV] Use tv text where necessary 2024-12-18 09:29:41 +01:00
Kamil Gurgul 523d6dd5b4 [TV] Add apps handling 2024-12-18 09:07:35 +01:00
Kamil Gurgul 3aca226373 [TV] Adjust settings dialogs 2024-12-17 09:51:36 +01:00
Kamil Gurgul 2422f33469 [TV] Add initial app list 2024-12-17 09:29:49 +01:00
Kamil Gurgul 06b60a25d0 [TV] Add NavigationDrawer 2024-12-16 09:29:02 +01:00
Kamil Gurgul 5d51914842 [SHARED] Update kover 2024-12-15 07:10:45 +01:00
Kamil Gurgul 61e8784dbb [TV] Adjust temperature screen 2024-12-14 09:11:35 +01:00
Kamil GurgulandGitHub 1d5265a0bd Initial Android TV support (#260)
* [ANDROID_TV] Try to create Android TV UI

* [SHARED] Update libs

* [SHARED] Remove magic numbers

* [SHARED] Change native libs icon to button

* [SHARED] Change selection color

* [SHARED] Fix apps action row

* [SHARED] Update kotlin

* [SHARED] Update xcode project structure, disable iOS arm simulator

* [ANDROID] Update native cpuinfo

* [IOS] Preparations for dependencies cleanup

* [SHARED] Update AGP

* [SHARED] Update navigation

* [IOS] Update cpuinfo lib

* [TV] Add TabRow for info section

* [TV] Handle info TabRow content

* [TV] Remove focusable workaround

* [TV] Add banner

* [TV] Adjust colors

* [TV] Adjust info screens
2024-12-13 09:17:41 +01:00
Kamil Gurgul 54c27ba534 [SHARED] Update libs 2024-12-12 07:46:05 +01:00
Kamil Gurgul 8b24fa3326 [IOS] Update cpuinfo lib 2024-12-10 07:46:53 +01:00
Kamil Gurgul 8a3f0dd809 [SHARED] Update navigation 2024-12-07 09:10:23 +01:00
Kamil Gurgul 6821d1f379 [SHARED] Update AGP 2024-12-03 07:11:03 +01:00
Kamil Gurgul 0c823faf5b [IOS] Preparations for dependencies cleanup 2024-11-28 19:12:24 +01:00
Kamil Gurgul 7caadb8d54 [ANDROID] Update native cpuinfo 2024-11-28 13:52:55 +01:00
Kamil Gurgul a2066c73b1 [SHARED] Update xcode project structure, disable iOS arm simulator 2024-11-28 13:41:45 +01:00
Kamil Gurgul 42198912a4 [SHARED] Update kotlin 2024-11-28 10:54:27 +01:00
Kamil Gurgul 51978b7bf9 [SHARED] Fix apps action row 2024-11-27 15:05:11 +01:00
Kamil Gurgul 7011c50040 [SHARED] Change selection color 2024-11-27 13:49:24 +01:00
Kamil Gurgul bffd099935 [SHARED] Change native libs icon to button 2024-11-27 13:18:07 +01:00
Kamil Gurgul f109e44271 [SHARED] Remove magic numbers 2024-11-27 10:48:02 +01:00
Kamil Gurgul 89ea48ffd9 [SHARED] Update libs 2024-11-27 09:06:22 +01:00
Kamil Gurgul bcd764308d [SHARED] Add content description for apps 2024-11-25 08:54:08 +01:00
Kamil Gurgul 88b232fd90 [SHARED] Add cpuinfo app url 2024-11-25 08:39:39 +01:00
Kamil Gurgul 610a8b7e6c [SHARED] Remove getString 2024-11-23 07:13:53 +01:00
Kamil Gurgul e8a75a102a [SHARED] Migrate sensor model to TextResource 2024-11-22 08:04:35 +01:00
Kamil Gurgul 4fd229cb47 [SHARED] Migrate temperature model to TextResource 2024-11-22 07:01:53 +01:00
Kamil Gurgul 81798596c8 [SHARED] Migrate storage model to TextResource 2024-11-21 08:53:16 +01:00
Kamil Gurgul 489c98ed01 [SHARED] Update libs 2024-11-21 07:02:25 +01:00
Kamil Gurgul 8ad8df921a [SHARED] Migrate OS info to ItemValue container 2024-11-21 06:53:38 +01:00
Kamil Gurgul a04723bdfe [SHARED] Migrate hardware info to ItemValue container 2024-11-20 11:36:27 +01:00
Kamil Gurgul daf407be5c [SHARED] Migrate GPU and screen info to ItemValue container 2024-11-20 08:10:38 +01:00
Kamil Gurgul 886314f153 [SHARED] Remove mockito 2024-11-19 09:56:32 +01:00
Kamil Gurgul 06c0a42186 [SHARED] Remove unused classes 2024-11-19 09:51:51 +01:00
Kamil Gurgul 383a69ad31 [SHARED] Migrate apps VM test to common 2024-11-19 09:50:19 +01:00
Kamil Gurgul 2aa6c4eb37 [SHARED] Migrate CPU VM test to common 2024-11-18 09:49:03 +01:00
Kamil Gurgul 98f8b6b826 [SHARED] Migrate ram VM test to common 2024-11-18 08:01:10 +01:00
Kamil Gurgul e2fb1a0479 [SHARED] Update libs 2024-11-17 09:23:01 +01:00
Kamil Gurgul 8d777e9046 [SHARED] Update DraggableBox elevation 2024-11-16 08:51:43 +01:00
Kamil Gurgul c81062992a [SHARED] Refactor ram cleanup tests 2024-11-16 08:49:45 +01:00
Kamil Gurgul 1956a3066a [SHARED] Migrate host VM test to common 2024-11-15 08:03:40 +01:00
Kamil Gurgul 98620999f6 [SHARED] Update coil 2024-11-15 07:52:42 +01:00
Kamil Gurgul 6b1d2f69b2 [SHARED] Refactor screen data tests 2024-11-15 07:49:05 +01:00
Kamil Gurgul c5ab58c49f [SHARED] Refactor temperature VM 2024-11-14 09:32:36 +01:00
Kamil Gurgul d45dd6ab19 [SHARED] Fix unit tests 2024-11-14 08:22:11 +01:00
Kamil Gurgul 511f6931b3 [SHARED] Adjust processes VM 2024-11-14 08:16:56 +01:00
Kamil Gurgul 60e43fdf45 [SHARED] Fix unit tests 2024-11-13 14:31:15 +01:00
Kamil Gurgul 870a28b64f [SHARED] Change OS data provider type 2024-11-13 09:00:12 +01:00
Kamil Gurgul aa6db44911 [SHARED] Adjust naming 2024-11-13 08:54:35 +01:00
Kamil Gurgul 26905f0742 [SHARED] Change init method for info VMs 2024-11-13 08:52:10 +01:00
Kamil Gurgul ff41a035d1 [SHARED] Update compose 2024-11-13 07:49:55 +01:00
Kamil Gurgul 28574c4737 [SHARED] Change host uiState initialization 2024-11-12 11:27:01 +01:00
Kamil Gurgul 2895da5ce8 [SHARED] Update gradle 2024-11-12 08:07:52 +01:00
Kamil Gurgul d179e598c5 [SHARED] Update coil 2024-11-10 09:38:25 +01:00
Kamil Gurgul d948f94642 [SHARED] Update README 2024-11-09 09:25:10 +01:00
Kamil Gurgul 4d62af638d [SHARED] Update libs 2024-11-08 07:13:16 +01:00
Kamil Gurgul d4c41cd362 [SHARED] Update libs 2024-11-06 07:27:22 +01:00
Kamil Gurgul b7b376123e [SHARED] Update coil 2024-11-05 09:14:04 +01:00
Kamil Gurgul 62c096ee3c [SHARED] Migrate DraggableBox to AnchoredDraggableState 2024-11-04 15:29:29 +01:00
Kamil Gurgul fd4837ed4b [SHARED] Simplify DraggableBox state 2024-11-04 10:55:36 +01:00
Kamil Gurgul fcd1467d93 [SHARED] Simplify DraggableBox state 2024-11-04 10:43:56 +01:00
Kamil Gurgul e861c81a60 [SHARED] Update agp 2024-11-01 10:11:18 +01:00
Kamil Gurgul 41815e9bf2 [SHARED] Update libs 2024-10-31 09:36:13 +01:00
Kamil Gurgul 9a24a4b934 [JVM] Change OSHI implementation 2024-10-31 09:34:42 +01:00
Kamil Gurgul 075cbf8516 [WASM] Add known issues to readme 2024-10-30 09:26:11 +01:00
Kamil Gurgul 191e8c6ef0 [ANDROID] Add OS language 2024-10-30 09:22:55 +01:00
Kamil Gurgul 4fc83c7acc [JVM] Add OS language 2024-10-30 09:21:24 +01:00
Kamil Gurgul e31127d595 [WASM] Add translations 2024-10-30 09:18:20 +01:00
Kamil Gurgul a71c50e836 [WASM] Add more providers 2024-10-30 09:13:39 +01:00
Kamil Gurgul 7d25c311a0 [WASM] Add string resources 2024-10-29 11:07:40 +01:00
Kamil Gurgul c4ca844851 [WASM] Code refactoring 2024-10-29 08:25:31 +01:00
Kamil Gurgul 4678e389d3 [SHARED] Update coil 2024-10-29 07:21:23 +01:00
Kamil Gurgul 468036440e [WASM] Add history length 2024-10-28 10:07:13 +01:00
Kamil Gurgul 10be67bd24 [ANDROID][#78] Add material you accent 2024-10-28 09:44:24 +01:00
Kamil Gurgul 62ed89eb54 [WASM] Add screen info 2024-10-28 06:54:20 +01:00
Kamil Gurgul 0de2d07f5f [WASM] Add cookies info 2024-10-27 08:59:10 +01:00
Kamil Gurgul ea3ec63943 [WASM] Add language data to OS 2024-10-25 11:12:24 +02:00
Kamil Gurgul 0a19b06a9d [WASM] Add navigator data to OS 2024-10-25 11:06:42 +02:00
Kamil Gurgul 5e55df428d [SHARED] Update ksp 2024-10-24 07:57:27 +02:00
Kamil Gurgul 6c4ce7361d [WASM] Move initKoin from composable 2024-10-24 07:51:51 +02:00
Kamil Gurgul 01ad482f61 [WASM] Add estimated storage 2024-10-23 11:42:30 +02:00
Kamil Gurgul 24cfabe841 [WASM] Add initial gpu provider, getString is failing on gpu provider 2024-10-23 10:55:34 +02:00
Kamil Gurgul 1a9dfd4b16 [WASM] Hardcode CPU data 2024-10-23 07:42:10 +02:00
Kamil Gurgul ff36d827a7 [WASM] Add heap size retrieval 2024-10-23 07:18:43 +02:00
Kamil Gurgul 1310043b67 [SHARED] Secure div operations 2024-10-22 11:00:08 +02:00
Kamil Gurgul 01488c7605 [WASM] JS function test, not working StateFlow on wasm 2024-10-22 10:50:16 +02:00
Kamil Gurgul 99baba9cba [WASM] Disable JS target, enable localStorage, add favicon 2024-10-22 07:39:20 +02:00
Kamil Gurgul 7f4aebdb73 [WASM] Simplify deps 2024-10-21 09:38:07 +02:00
Kamil Gurgul 4696c38514 [SHARED] Bring back old insets 2024-10-19 21:07:31 +02:00
Kamil Gurgul ce1a9be259 [SHARED] Fix and disable js tests for CI 2024-10-19 20:17:40 +02:00
Kamil Gurgul 5397d7a085 [WASM] Fix preview 2024-10-19 07:31:27 +02:00
Kamil Gurgul a3814cb6a1 [WASM] Fix unit tests names 2024-10-19 07:31:04 +02:00
Kamil Gurgul f571558813 [WASM] Fix alertdialog 2024-10-19 07:27:10 +02:00
Kamil Gurgul 4e29b00eac [WASM] Enable JS target 2024-10-18 09:04:05 +02:00
Kamil Gurgul 9487bbd680 [SHARED] Update NavigationSuiteScaffold impl 2024-10-18 07:41:27 +02:00
Kamil Gurgul a83749f160 [WASM] Enable wasm 2024-10-18 07:12:17 +02:00
Kamil Gurgul e3fa431b06 [SHARED] Create mobile main, add base files for wasm 2024-10-17 09:47:47 +02:00
Kamil Gurgul d646ad0921 [SHARED] Temporary exclude DataStore from common to enable wasm 2024-10-17 09:14:39 +02:00
Kamil Gurgul a629ec0067 [SHARED] Update libs 2024-10-17 08:25:39 +02:00
Kamil Gurgul 067f568d1c [SHARED] Update readme 2024-10-16 09:41:40 +02:00
Kamil Gurgul 5dd7fd23be [SHARED] Add koin dependency verification 2024-10-16 09:38:16 +02:00
Kamil Gurgul 961433eebe [SHARED] Update libs 2024-10-16 09:07:36 +02:00
Kamil Gurgul f83c50ef40 [SHARED] Remove koin annotations for wasm support 2024-10-16 07:48:34 +02:00
Kamil Gurgul 85ff28c2db [SHARED] Add selection functionality 2024-10-15 09:55:45 +02:00
Kamil Gurgul 47a7aeb224 [SHARED] Upgrade AGP 2024-10-15 09:24:50 +02:00
Kamil Gurgul f243e80997 [Shared] Add elevation transition to DraggableBox 2024-10-13 07:32:26 +02:00
Kamil Gurgul 84c7617063 [Shared] Update kotlin 2024-10-12 07:06:39 +02:00
Kamil Gurgul 982fed28e0 [Shared] Update coil 2024-10-09 07:01:51 +02:00
Kamil Gurgul 52d9292837 [Android] Bump BP minSdk 2024-10-07 10:36:16 +02:00
Kamil Gurgul 0db2fabef4 [Android] Bump Android version 2024-10-04 06:44:28 +02:00
Kamil Gurgul 52531b46f0 [SHARED] Upgrade JDK to 17, bump version for fdroid 2024-10-03 10:13:50 +02:00
Kamil Gurgul 7408e67789 [SHARED] Update libs 2024-10-03 06:41:33 +02:00
Kamil Gurgul 9612d86545 [SHARED] Update libs 2024-10-02 08:10:37 +02:00
Kamil Gurgul fed6475003 [SHARED] Format code with ktlint 2024-10-02 06:44:58 +02:00
Kamil Gurgul aed463219a [SHARED] Add editorconfig file 2024-10-01 07:42:13 +02:00
Kamil Gurgul e6574ede9d [SHARED] Update oshi 2024-09-29 13:42:39 +02:00
Kamil Gurgul c89bc7c42a [SHARED] Update gradle to 8.10.2 2024-09-27 09:46:27 +02:00
Kamil Gurgul 29ba041279 [ANDROID] Code refactoring 2024-09-26 08:53:12 +02:00
Kamil Gurgul ebde0efa54 [ANDROID] Code refactor 2024-09-26 07:47:21 +02:00
Kamil Gurgul c58f6249f1 [SHARED] Move TemperatureDataObservableTest to shared 2024-09-26 06:50:22 +02:00
Kamil Gurgul 40f98870d8 [SHARED] Move ProcessesDataObservableTest to shared 2024-09-25 09:27:38 +02:00
Kamil Gurgul 04f414606e [SHARED] Move test data to shared 2024-09-25 09:13:01 +02:00
Kamil Gurgul 250a86b07e [SHARED] Update translations 2024-09-23 10:08:49 +02:00
Kamil Gurgul 490a592445 [ANDROID] Update release action 2024-09-23 09:49:52 +02:00
Kamil Gurgul 225b2f5412 [ANDROID] Try to fix BP 2024-09-23 09:29:08 +02:00
Kamil Gurgul 5b009fc416 [ANDROID] Try to fix BP 2024-09-23 09:00:21 +02:00
Kamil Gurgul 6211b43ea5 [ANDROID] Try to fix BP 2024-09-23 08:03:32 +02:00
Kamil Gurgul 91e2da9f48 [SHARED] Start test migrations to shared 2024-09-23 08:01:19 +02:00
Kamil Gurgul 4a699fea63 [ANDROID] Try to fix baselineprofile 2024-09-22 10:57:51 +02:00
Kamil Gurgul 3b104c72ab [ANDROID] Try to fix baselineprofile 2024-09-22 10:47:34 +02:00
Kamil Gurgul 157341b14a [ANDROID] Try to fix baselineprofile 2024-09-22 09:34:33 +02:00
Kamil Gurgul 327c90c55a [ANDROID] Try to fix baselineprofile 2024-09-21 14:27:45 +02:00
Kamil Gurgul 4e364fa704 [ANDROID] Try to fix baselineprofile 2024-09-21 14:24:47 +02:00
Kamil Gurgul 079178a02b [ANDROID] Try to fix baselineprofile 2024-09-21 14:18:10 +02:00
Kamil Gurgul 1ceb6e4aad [ANDROID] Try to fix baselineprofile 2024-09-21 14:05:02 +02:00
Kamil Gurgul 1e955fcaf5 [ANDROID] Try to fix baselineprofile 2024-09-21 11:05:51 +02:00
Kamil Gurgul c353b25664 [SHARED] Fix progress bar state 2024-09-20 10:09:19 +02:00
Kamil Gurgul 8a9d832813 [ANDROID] Update libs 2024-09-19 07:22:35 +02:00
Kamil Gurgul 882aa159ef [ANDROID] Update version 2024-09-19 07:18:51 +02:00
Kamil Gurgul 35d0a5102c [ANDROID] Update AGP 2024-09-18 07:21:10 +02:00
Kamil Gurgul 28b77ddbb5 [SHARED] Update koin 2024-09-17 16:15:07 +02:00
Kamil Gurgul ee2afe042a [SHARED] Update oshi 2024-09-16 10:00:08 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
80eb19a118 Update kotlinxCoroutines to v1.9.0 (#208)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-13 18:57:37 +02:00
Kamil Gurgul e17ed72439 [SHARED] Update README.md 2024-09-13 10:45:37 +02:00
Kamil Gurgul 670b84bf2a [SHARED] Update README.md 2024-09-13 10:43:49 +02:00
Kamil Gurgul 6af8795ca2 [ANDROID] Bump target and compile SDK to 35, NDK to r27, add support for 16KB page size 2024-09-13 09:19:11 +02:00
Kamil Gurgul 2423844b8d [SHARED] Add animation to processes 2024-09-11 19:21:19 +02:00
Kamil Gurgul b0a464ab0a [SHARED] Update libs 2024-09-11 17:03:37 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
a221864770 Update dependency com.google.devtools.ksp to v2.0.20-1.0.25 (#205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-06 07:49:01 +02:00
Kamil Gurgul 4ed008976e [SHARED] Remove window lib workaround, enable item change animation for apps list 2024-09-06 07:47:37 +02:00
Kamil Gurgul 8c863c92a3 [SHARED] Update utils 2024-09-05 19:29:21 +02:00
Kamil Gurgul af68af14e7 [SHARED] Update android libs 2024-09-05 19:28:39 +02:00
Kamil Gurgul 2c136bc222 [SHARED] Update compose 2024-09-05 19:26:34 +02:00
Kamil Gurgul d7928e36d4 [SHARED] Add corruption handler to DataStore 2024-09-04 14:24:57 +02:00
Kamil Gurgul 2db387892a [DESKTOP] Update publish task 2024-09-03 07:53:08 +02:00
Kamil Gurgul 57c24c4933 [DESKTOP] Update notarize task 2024-09-03 07:40:22 +02:00
Kamil Gurgul a60ec4d645 [DESKTOP] Update notarize task 2024-09-03 07:25:33 +02:00
Kamil Gurgul 7849b450e8 [DESKTOP] Update notarize task 2024-09-03 06:53:07 +02:00
Kamil Gurgul bf679a9c4e [DESKTOP] Add notarized mac build 2024-09-03 06:24:35 +02:00
Kamil Gurgul b83f70afbe [DESKTOP] Update mac entitlements - probably show stopper for app store 2024-09-02 06:41:44 +02:00
Kamil Gurgul 5e4cc29087 [DESKTOP] Update mac icon and entitlements 2024-09-02 06:13:53 +02:00
Kamil Gurgul c185be0e5c [DESKTOP] Base for Mac App Store build v4 2024-09-01 18:31:04 +02:00
Kamil Gurgul 817a5b102f [DESKTOP] Base for Mac App Store build v3 2024-08-31 07:32:11 +02:00
Kamil Gurgul 2d3f7d8853 [DESKTOP] Base for Mac App Store build v3 2024-08-31 07:28:35 +02:00
Kamil Gurgul f2276cdde3 [DESKTOP] Base for Mac App Store build v2 2024-08-30 20:46:50 +02:00
Kamil GurgulandGitHub e5c9281c51 Update libs.versions.toml 2024-08-30 19:39:23 +02:00
Kamil Gurgul b4293c69b4 [DESKTOP] Base for Mac App Store build 2024-08-29 12:49:00 +02:00
Kamil Gurgul b064cb857f [DESKTOP] Setup provisioning for Mac App Store 2024-08-28 12:53:47 +02:00
Kamil Gurgul 352dca246e [SHARED] Enforce UiState stability 2024-08-28 09:46:46 +02:00
Kamil Gurgul 42ea6516c7 [SHARED] Update compose metrics configuration 2024-08-28 09:29:56 +02:00
Kamil Gurgul 06ce70131a [SHARED] Update README.md 2024-08-28 08:43:16 +02:00
Kamil Gurgul 32009bffd5 [ANDROID] Update baseline profile config 2024-08-27 12:41:11 +02:00
Kamil Gurgul 80400634e5 [SHARED] Remove unnecessary DI config 2024-08-27 12:09:17 +02:00
Kamil Gurgul 58b63e899a [SHARED] Remove unused annotation 2024-08-27 09:42:36 +02:00
Kamil Gurgul 131b362fb4 [SHARED] Add processes sorting 2024-08-27 08:58:01 +02:00
Kamil Gurgul c8c25e9d52 [SHARED] Add Flathub badge 2024-08-27 08:02:47 +02:00
Kamil Gurgul 2fae678e2e [SHARED] Drop Material 2 2024-08-27 06:41:44 +02:00
Kamil Gurgul 06f97b68b7 [SHARED] Add HorizontalScrollbar to ScrollableTabRow 2024-08-26 11:13:42 +02:00
Kamil Gurgul a71cd1d183 [SHARED] Update README.md 2024-08-26 10:14:53 +02:00
Kamil Gurgul 508ccb8079 [SHARED] Update README.md 2024-08-24 09:39:58 +02:00
Kamil Gurgul 477683db4f [SHARED] Migrate NavigationSuiteScaffold to compose-multiplatform adaptive impl 2024-08-23 09:04:52 +02:00
Kamil Gurgul 11b16b8edd [SHARED] Update kotlin 2024-08-23 08:22:06 +02:00
Kamil Gurgul a5a326d0da [DESKTOP] Add flathub meta files 2024-08-23 07:12:29 +02:00
Kamil Gurgul 19df5897f4 [DESKTOP] Bump desktop app 2024-08-22 13:45:13 +02:00
kamgurgul 65918143ad [DESKTOP] Add linux screenshots 2024-08-22 10:08:03 +02:00
Kamil Gurgul bc22de91a1 [DESKTOP] Bump desktop app 2024-08-22 09:59:29 +02:00
kamgurgul a1284f9111 [DESKTOP] Fix storage info duplicates 2024-08-22 09:56:54 +02:00
Kamil Gurgul f88f2d732a [SHARED] Update kover config 2024-08-22 07:09:36 +02:00
Kamil Gurgul e9f9922750 [DESKTOP] Remove additional cache from script 2024-08-22 06:54:36 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ce6c88a4db Update dependency androidx.baselineprofile to v1.3.0 (#193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 06:48:42 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
edb82bed8a Update dependency androidx.benchmark:benchmark-macro-junit4 to v1.3.0 (#194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 06:43:34 +02:00
Kamil Gurgul 4b4c3b2bfa [DESKTOP] Add scrollbars 2024-08-21 10:12:29 +02:00
Kamil Gurgul 2a57366029 [DESKTOP] Add Linux x64 to release github actions 2024-08-21 09:17:06 +02:00
Kamil Gurgul 510391593c [DESKTOP] Workaround RPI issue 2024-08-21 08:40:52 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
a4aa57a6a4 Update dependency com.github.oshi:oshi-core to v6.6.3 (#191)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 08:22:47 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
dbcc84f65b Update dependency org.jetbrains.androidx.navigation:navigation-compose to v2.8.0-alpha09 (#192)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 08:22:34 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8342fd2cbe Update dependency org.jetbrains.compose to v1.7.0-alpha03 (#190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 08:22:16 +02:00
Kamil Gurgul 0cf5eeaebd [SHARED] Downgrade gradle 2024-08-21 08:20:29 +02:00
Kamil Gurgul e7e7ac415c [SHARED] Update processes screen 2024-08-21 08:19:04 +02:00
Kamil Gurgul 0bfd224bdb [SHARED] Update tagging 2024-08-20 07:24:44 +02:00
Kamil Gurgul d830ba4a7d [DESKTOP] Fix github actions 2024-08-19 07:50:32 +02:00
Kamil Gurgul 4969064f17 [SHARED] Update github actions 2024-08-19 07:45:34 +02:00
Kamil Gurgul fefc23d2d9 [DESKTOP] base script for desktop release build 2024-08-18 15:23:57 +02:00
Kamil Gurgul 1f70d680dc [SHARED] Update gradle to 8.10 2024-08-18 15:00:04 +02:00
kamgurgul 4d6bfe8008 Revert "[SHARED] Update README.md"
This reverts commit 67f910e278.
2024-08-16 01:17:37 +02:00
kamgurgul 67f910e278 [SHARED] Update README.md 2024-08-16 01:16:48 +02:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ec897c9467 Update dependency androidx.test:runner to v1.6.2 (#189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-15 09:21:59 +02:00
kamgurgul 816161eda7 [SHARED] Update README.md 2024-08-15 09:20:57 +02:00
Kamil Gurgul 6a741a46b8 [DESKTOP] Code formatting 2024-08-14 07:04:38 +02:00
473 changed files with 17925 additions and 47215 deletions
+1
View File
@@ -0,0 +1 @@
local.properties
+19
View File
@@ -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
+30
View File
@@ -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 -->
+19
View File
@@ -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
+27
View File
@@ -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.
+63 -13
View File
@@ -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 }}
+248
View File
@@ -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 }}
+6 -6
View File
@@ -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 }}
+83
View File
@@ -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 }}
+3 -3
View File
@@ -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
+122
View File
@@ -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 }}'
+21 -14
View File
@@ -1,10 +1,10 @@
![Build and test](https://github.com/kamgurgul/cpu-info/actions/workflows/shared_test.yml/badge.svg)
<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
-2
View File
@@ -1,3 +1 @@
/build
src/debug/google-services.json
src/release/google-services.json
+17 -8
View File
@@ -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()
}
},
)
}
}
}
+39 -64
View File
@@ -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")
}
}
}
}
+36 -1
View File
@@ -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>
Binary file not shown.

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>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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() {
}
}
}
+3 -19
View File
@@ -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)
}
}
}
}
+18
View File
@@ -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"
}
+2 -1
View File
@@ -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
+1 -1
View File
@@ -15,4 +15,4 @@ object KoverConfig {
"*_Factory*",
)
val excludedAnnotations = "*Generated*"
}
}
-15
View File
@@ -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"
}
+39 -14
View File
@@ -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")
}
}
}
}
Binary file not shown.
+24
View File
@@ -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>
+24 -1
View File
@@ -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.** { *; }
+24
View File
@@ -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>
+8 -7
View File
@@ -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
+48 -31
View File
@@ -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" }
Binary file not shown.
+1 -1
View File
@@ -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
Vendored
+4 -2
View File
@@ -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.
Vendored
+2
View File
@@ -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 ##########################################################################
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+8 -8
View File
@@ -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 = "";
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -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")
+134 -74
View File
@@ -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")
}
}
}
}
Binary file not shown.
@@ -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,
)
}
}
}

Some files were not shown because too many files have changed in this diff Show More