Hoist state, base for events handling
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
<img src="info/icon_glow.png" width="100" height="100" />
|
||||
|
||||
CPU Info<br/>
|
||||
========
|
||||
CPU Info (refactor-in-progress ⛏)
|
||||
=================================
|
||||
CPU Info provides information about Android device hardware and software.
|
||||
Most of the code is written in Kotlin but some old widgets are still in
|
||||
Java.
|
||||
|
||||
Due to lack of time, this project will be updated sporadically. Right now it is only a sandbox.
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/com.kgurgul.cpuinfo/)
|
||||
@@ -17,6 +15,7 @@ Due to lack of time, this project will be updated sporadically. Right now it is
|
||||
|
||||
Still TODO
|
||||
==========
|
||||
* Unify architecture - add interactors and data providers
|
||||
* Replace layouts with Compose
|
||||
* Replace Travis
|
||||
* Replace RxJava with coroutines
|
||||
|
||||
@@ -3,12 +3,11 @@ package com.kgurgul.cpuinfo.features.applications
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -25,38 +24,61 @@ import com.kgurgul.cpuinfo.theme.CpuInfoTheme
|
||||
import com.kgurgul.cpuinfo.utils.wrappers.Result
|
||||
|
||||
@Composable
|
||||
fun ApplicationsScreen(viewModel: NewApplicationsViewModel = viewModel()) {
|
||||
fun ApplicationsScreen(
|
||||
viewModel: NewApplicationsViewModel = viewModel(),
|
||||
scaffoldState: ScaffoldState = rememberScaffoldState(),
|
||||
onAppClicked: (packageName: String) -> Unit
|
||||
) {
|
||||
val uiState by viewModel.applicationList.collectAsState()
|
||||
|
||||
val isRefreshingState = uiState is Result.Loading
|
||||
Surface {
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = scaffoldState
|
||||
) {
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(isRefreshingState),
|
||||
onRefresh = { viewModel.refreshApplications() },
|
||||
) {
|
||||
(uiState as? Result.Success)?.let {
|
||||
ApplicationsList(state = it)
|
||||
ApplicationsList(
|
||||
appList = it.data,
|
||||
onAppClicked = onAppClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ApplicationsList(state: Result.Success<List<ExtendedApplicationData>>) {
|
||||
fun ApplicationsList(
|
||||
appList: List<ExtendedApplicationData>,
|
||||
onAppClicked: (packageName: String) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
items(state.data) {
|
||||
ApplicationItem(appData = it)
|
||||
items(appList) {
|
||||
ApplicationItem(
|
||||
appData = it,
|
||||
onAppClicked = onAppClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ApplicationItem(appData: ExtendedApplicationData) {
|
||||
fun ApplicationItem(
|
||||
appData: ExtendedApplicationData,
|
||||
onAppClicked: (packageName: String) -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { onAppClicked(appData.packageName) })
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = rememberImagePainter(
|
||||
@@ -85,7 +107,7 @@ fun ApplicationItem(appData: ExtendedApplicationData) {
|
||||
fun ApplicationInfoPreviewLight() {
|
||||
CpuInfoTheme {
|
||||
Surface {
|
||||
ApplicationItem(testAppData)
|
||||
ApplicationItem(previewAppData) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,12 +117,12 @@ fun ApplicationInfoPreviewLight() {
|
||||
fun ApplicationInfoPreviewDark() {
|
||||
CpuInfoTheme {
|
||||
Surface {
|
||||
ApplicationItem(testAppData)
|
||||
ApplicationItem(previewAppData) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val testAppData = ExtendedApplicationData(
|
||||
private val previewAppData = ExtendedApplicationData(
|
||||
"Cpu Info",
|
||||
"com.kgurgul.cpuinfo",
|
||||
"/testDir",
|
||||
|
||||
+7
-1
@@ -6,6 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.kgurgul.cpuinfo.R
|
||||
import com.kgurgul.cpuinfo.theme.CpuInfoTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -13,6 +14,8 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
@AndroidEntryPoint
|
||||
class NewApplicationsFragment : Fragment() {
|
||||
|
||||
private val viewModel: NewApplicationsViewModel by viewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -26,7 +29,10 @@ class NewApplicationsFragment : Fragment() {
|
||||
)
|
||||
setContent {
|
||||
CpuInfoTheme {
|
||||
ApplicationsScreen()
|
||||
ApplicationsScreen(
|
||||
viewModel = viewModel,
|
||||
onAppClicked = viewModel::onApplicationClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -2,12 +2,17 @@ package com.kgurgul.cpuinfo.features.applications
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.kgurgul.cpuinfo.R
|
||||
import com.kgurgul.cpuinfo.domain.model.sortOrderFromBoolean
|
||||
import com.kgurgul.cpuinfo.domain.observable.ApplicationsDataObservable
|
||||
import com.kgurgul.cpuinfo.utils.wrappers.Result
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -18,6 +23,9 @@ class NewApplicationsViewModel @Inject constructor(
|
||||
val applicationList = applicationsDataObservable.observe()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), Result.Loading)
|
||||
|
||||
private val eventChannel = Channel<Event>(Channel.BUFFERED)
|
||||
val eventsFlow = eventChannel.receiveAsFlow()
|
||||
|
||||
init {
|
||||
refreshApplications()
|
||||
}
|
||||
@@ -30,4 +38,15 @@ class NewApplicationsViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun onApplicationClicked(packageName: String) {
|
||||
viewModelScope.launch {
|
||||
eventChannel.send(Event.ShowSnackbar(R.string.cpu_open))
|
||||
}
|
||||
Timber.d("App clicked: %s", packageName)
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
data class ShowSnackbar(val messageRes: Int) : Event()
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,10 @@ class UtilsTest {
|
||||
@Test
|
||||
fun humanReadableByteCountFormatting() {
|
||||
/* When */
|
||||
val bytes = Utils.humanReadableByteCount(10)
|
||||
val kilo = Utils.humanReadableByteCount(1500)
|
||||
val mega = Utils.humanReadableByteCount(1500 * 1024)
|
||||
val giga = Utils.humanReadableByteCount(1500 * 1024 * 1024)
|
||||
val bytes = Utils.humanReadableByteCount(10L)
|
||||
val kilo = Utils.humanReadableByteCount(1500L)
|
||||
val mega = Utils.humanReadableByteCount(1500L * 1024L)
|
||||
val giga = Utils.humanReadableByteCount(1500L * 1024L * 1024L)
|
||||
val teta = Utils.humanReadableByteCount(1500L * 1024L * 1024L * 1024L)
|
||||
val peta = Utils.humanReadableByteCount(1500L * 1024L * 1024L * 1024L * 1024L)
|
||||
val e = Utils.humanReadableByteCount(1500L * 1024L * 1024L * 1024L * 1024L * 1024L)
|
||||
@@ -68,10 +68,10 @@ class UtilsTest {
|
||||
@Test
|
||||
fun bytesToMegaFormatting() {
|
||||
/* When */
|
||||
val bytes = Utils.convertBytesToMega(10)
|
||||
val kilo = Utils.convertBytesToMega(1500)
|
||||
val mega = Utils.convertBytesToMega(1500 * 1024)
|
||||
val giga = Utils.convertBytesToMega(1500 * 1024 * 1024)
|
||||
val bytes = Utils.convertBytesToMega(10L)
|
||||
val kilo = Utils.convertBytesToMega(1500L)
|
||||
val mega = Utils.convertBytesToMega(1500L * 1024L)
|
||||
val giga = Utils.convertBytesToMega(1500L * 1024L * 1024L)
|
||||
|
||||
/* Then */
|
||||
assertEquals("0 MB", bytes)
|
||||
|
||||
Reference in New Issue
Block a user