manager: replace SwipeRefresh with PullRefreshIndicator & refactor so… (#288)
- replace SwipeRefresh with PullRefreshIndicator - optimize pull refresh - refactor some code - fix install bottom in module page again
This commit is contained in:
@@ -62,10 +62,10 @@ dependencies {
|
|||||||
val accompanistVersion = "0.28.0"
|
val accompanistVersion = "0.28.0"
|
||||||
val composeDestinationsVersion = "1.7.27-beta"
|
val composeDestinationsVersion = "1.7.27-beta"
|
||||||
implementation(platform("androidx.compose:compose-bom:2022.12.00"))
|
implementation(platform("androidx.compose:compose-bom:2022.12.00"))
|
||||||
|
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
implementation("androidx.activity:activity-compose:1.6.1")
|
implementation("androidx.activity:activity-compose:1.6.1")
|
||||||
|
implementation("androidx.compose.material:material:1.4.0-beta02")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
@@ -75,7 +75,6 @@ dependencies {
|
|||||||
implementation("androidx.navigation:navigation-compose:2.5.3")
|
implementation("androidx.navigation:navigation-compose:2.5.3")
|
||||||
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
|
||||||
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
|
||||||
implementation("com.google.accompanist:accompanist-swiperefresh:$accompanistVersion")
|
|
||||||
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
|
||||||
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
|
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
||||||
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
||||||
import me.weishu.kernelsu.ui.screen.NavGraphs
|
import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||||
import me.weishu.kernelsu.ui.screen.appCurrentDestinationAsState
|
import me.weishu.kernelsu.ui.screen.appCurrentDestinationAsState
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.Destination
|
import me.weishu.kernelsu.ui.screen.destinations.Destination
|
||||||
import me.weishu.kernelsu.ui.screen.startAppDestination
|
import me.weishu.kernelsu.ui.screen.startAppDestination
|
||||||
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
||||||
|
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -38,7 +40,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
bottomBar = { BottomBar(navController) },
|
bottomBar = { BottomBar(navController) },
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
CompositionLocalProvider(LocalSnackbarHost provides snackbarHostState) {
|
CompositionLocalProvider(
|
||||||
|
LocalSnackbarHost provides snackbarHostState,
|
||||||
|
LocalDialogHost provides rememberDialogHostState(),
|
||||||
|
) {
|
||||||
DestinationsNavHost(
|
DestinationsNavHost(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
navGraph = NavGraphs.root,
|
navGraph = NavGraphs.root,
|
||||||
|
|||||||
@@ -8,8 +8,12 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -17,14 +21,13 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -32,7 +35,6 @@ import me.weishu.kernelsu.Natives
|
|||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
||||||
import me.weishu.kernelsu.ui.component.DialogResult
|
import me.weishu.kernelsu.ui.component.DialogResult
|
||||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.*
|
import me.weishu.kernelsu.ui.util.*
|
||||||
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||||
@@ -42,9 +44,6 @@ import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun ModuleScreen(navigator: DestinationsNavigator) {
|
fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||||
val viewModel = viewModel<ModuleViewModel>()
|
val viewModel = viewModel<ModuleViewModel>()
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.moduleList.isEmpty()) {
|
if (viewModel.moduleList.isEmpty()) {
|
||||||
@@ -53,9 +52,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isSafeMode = Natives.isSafeMode()
|
val isSafeMode = Natives.isSafeMode()
|
||||||
|
val isKSUVersionInvalid = Natives.getVersion() < 0
|
||||||
val hasMagisk = hasMagisk()
|
val hasMagisk = hasMagisk()
|
||||||
|
|
||||||
val hideInstallButton = isSafeMode || hasMagisk
|
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk
|
||||||
|
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
TopBar()
|
TopBar()
|
||||||
@@ -91,54 +91,46 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}) { innerPadding ->
|
}) { innerPadding ->
|
||||||
|
|
||||||
val dialogState = rememberDialogHostState()
|
ConfirmDialog()
|
||||||
ConfirmDialog(dialogState)
|
|
||||||
|
|
||||||
|
when {
|
||||||
|
isKSUVersionInvalid -> {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Text(stringResource(R.string.require_kernel_version_8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasMagisk -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(24.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.module_magisk_conflict),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ModuleList(
|
||||||
|
viewModel = viewModel,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier) {
|
||||||
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||||
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
||||||
val failedUninstall = stringResource(R.string.module_uninstall_failed)
|
val failedUninstall = stringResource(R.string.module_uninstall_failed)
|
||||||
val successUninstall = stringResource(R.string.module_uninstall_success)
|
val successUninstall = stringResource(R.string.module_uninstall_success)
|
||||||
val swipeState = rememberSwipeRefreshState(viewModel.isRefreshing)
|
|
||||||
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
|
||||||
if (Natives.getVersion() < 8) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
Text(stringResource(R.string.require_kernel_version_8))
|
|
||||||
}
|
|
||||||
return@Scaffold
|
|
||||||
}
|
|
||||||
if (hasMagisk) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
|
|
||||||
Text(stringResource(R.string.module_magisk_conflict))
|
|
||||||
}
|
|
||||||
return@Scaffold
|
|
||||||
}
|
|
||||||
SwipeRefresh(
|
|
||||||
state = swipeState, onRefresh = {
|
|
||||||
scope.launch { viewModel.fetchModuleList() }
|
|
||||||
}, modifier = Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
val isOverlayAvailable = overlayFsAvailable()
|
|
||||||
if (!isOverlayAvailable) {
|
|
||||||
swipeState.isRefreshing = false
|
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
Text(stringResource(R.string.module_overlay_fs_not_available))
|
|
||||||
}
|
|
||||||
return@SwipeRefresh
|
|
||||||
}
|
|
||||||
val isEmpty = viewModel.moduleList.isEmpty()
|
|
||||||
if (isEmpty) {
|
|
||||||
swipeState.isRefreshing = false
|
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
Text(stringResource(R.string.module_empty))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(15.dp),
|
|
||||||
contentPadding = remember { PaddingValues(bottom = 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */) }) {
|
|
||||||
items(viewModel.moduleList) { module ->
|
|
||||||
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
|
|
||||||
val reboot = stringResource(id = R.string.reboot)
|
val reboot = stringResource(id = R.string.reboot)
|
||||||
val rebootToApply = stringResource(id = R.string.reboot_to_apply)
|
val rebootToApply = stringResource(id = R.string.reboot_to_apply)
|
||||||
val moduleStr = stringResource(id = R.string.module)
|
val moduleStr = stringResource(id = R.string.module)
|
||||||
@@ -146,16 +138,19 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
val cancel = stringResource(id = android.R.string.cancel)
|
val cancel = stringResource(id = android.R.string.cancel)
|
||||||
val moduleUninstallConfirm =
|
val moduleUninstallConfirm =
|
||||||
stringResource(id = R.string.module_uninstall_confirm)
|
stringResource(id = R.string.module_uninstall_confirm)
|
||||||
ModuleItem(module, isChecked, onUninstall = {
|
|
||||||
scope.launch {
|
val dialogHost = LocalDialogHost.current
|
||||||
val dialogResult = dialogState.showDialog(
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
|
|
||||||
|
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
||||||
|
val dialogResult = dialogHost.showDialog(
|
||||||
moduleStr,
|
moduleStr,
|
||||||
content = moduleUninstallConfirm.format(module.name),
|
content = moduleUninstallConfirm.format(module.name),
|
||||||
confirm = uninstall,
|
confirm = uninstall,
|
||||||
dismiss = cancel
|
dismiss = cancel
|
||||||
)
|
)
|
||||||
if (dialogResult != DialogResult.Confirmed) {
|
if (dialogResult != DialogResult.Confirmed) {
|
||||||
return@launch
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val success = uninstallModule(module.id)
|
val success = uninstallModule(module.id)
|
||||||
@@ -172,13 +167,39 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val result = snackBarHost.showSnackbar(
|
val result = snackBarHost.showSnackbar(message, actionLabel = actionLabel)
|
||||||
message, actionLabel = actionLabel
|
|
||||||
)
|
|
||||||
if (result == SnackbarResult.ActionPerformed) {
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
reboot()
|
reboot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val refreshState = rememberPullRefreshState(
|
||||||
|
refreshing = viewModel.isRefreshing,
|
||||||
|
onRefresh = { viewModel.fetchModuleList() }
|
||||||
|
)
|
||||||
|
Box(modifier.pullRefresh(refreshState).padding(16.dp)) {
|
||||||
|
if (viewModel.isOverlayAvailable) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(15.dp),
|
||||||
|
contentPadding = remember { PaddingValues(bottom = 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */) },
|
||||||
|
) {
|
||||||
|
val isEmpty = viewModel.moduleList.isEmpty()
|
||||||
|
if (isEmpty) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.module_empty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items(viewModel.moduleList) { module ->
|
||||||
|
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
ModuleItem(module, isChecked, onUninstall = {
|
||||||
|
scope.launch { onModuleUninstall(module) }
|
||||||
}, onCheckChanged = {
|
}, onCheckChanged = {
|
||||||
val success = toggleModule(module.id, !isChecked)
|
val success = toggleModule(module.id, !isChecked)
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -203,8 +224,20 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Text(stringResource(R.string.module_overlay_fs_not_available))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = viewModel.isRefreshing,
|
||||||
|
state = refreshState,
|
||||||
|
modifier = Modifier.align(
|
||||||
|
Alignment.TopCenter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package me.weishu.kernelsu.ui.screen
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
@@ -17,12 +16,12 @@ import com.alorma.compose.settings.ui.*
|
|||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
|
||||||
import me.weishu.kernelsu.BuildConfig
|
import me.weishu.kernelsu.BuildConfig
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.SimpleDialog
|
import me.weishu.kernelsu.ui.component.SimpleDialog
|
||||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
|
||||||
import me.weishu.kernelsu.ui.util.LinkifyText
|
import me.weishu.kernelsu.ui.util.LinkifyText
|
||||||
|
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
||||||
|
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,9 +41,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
|
||||||
val dialogState = rememberDialogHostState()
|
SimpleDialog {
|
||||||
|
|
||||||
SimpleDialog(dialogState) {
|
|
||||||
SupportCard()
|
SupportCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +72,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
val about = stringResource(id = R.string.about)
|
val about = stringResource(id = R.string.about)
|
||||||
val ok = stringResource(id = android.R.string.ok)
|
val ok = stringResource(id = android.R.string.ok)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val dialogHost = LocalDialogHost.current
|
||||||
SettingsMenuLink(title = {
|
SettingsMenuLink(title = {
|
||||||
Text(about)
|
Text(about)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
dialogState.showDialog(about, content = "unused", confirm = ok)
|
dialogHost.showDialog(about, content = "unused", confirm = ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,11 +3,16 @@ package me.weishu.kernelsu.ui.screen
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -15,8 +20,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
@@ -26,7 +29,7 @@ import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
|||||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun SuperUserScreen() {
|
fun SuperUserScreen() {
|
||||||
@@ -87,19 +90,19 @@ fun SuperUserScreen() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
|
||||||
|
|
||||||
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
val refreshState = rememberPullRefreshState(
|
||||||
SwipeRefresh(
|
refreshing = viewModel.isRefreshing,
|
||||||
state = rememberSwipeRefreshState(viewModel.isRefreshing),
|
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
||||||
onRefresh = {
|
)
|
||||||
scope.launch { viewModel.fetchAppList() }
|
Box(
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.fillMaxSize()
|
.pullRefresh(refreshState)
|
||||||
) {
|
) {
|
||||||
LazyColumn {
|
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
|
||||||
|
|
||||||
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(viewModel.appList, key = { it.packageName }) { app ->
|
items(viewModel.appList, key = { it.packageName }) { app ->
|
||||||
var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) }
|
var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) }
|
||||||
AppItem(app, isChecked) { checked ->
|
AppItem(app, isChecked) { checked ->
|
||||||
@@ -112,6 +115,12 @@ fun SuperUserScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = viewModel.isRefreshing,
|
||||||
|
state = refreshState,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.launch
|
||||||
import me.weishu.kernelsu.ui.util.listModules
|
import me.weishu.kernelsu.ui.util.listModules
|
||||||
|
import me.weishu.kernelsu.ui.util.overlayFsAvailable
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -36,6 +38,9 @@ class ModuleViewModel : ViewModel() {
|
|||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var isOverlayAvailable by mutableStateOf(overlayFsAvailable())
|
||||||
|
private set
|
||||||
|
|
||||||
val moduleList by derivedStateOf {
|
val moduleList by derivedStateOf {
|
||||||
val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||||
modules.sortedWith(comparator).also {
|
modules.sortedWith(comparator).also {
|
||||||
@@ -43,12 +48,16 @@ class ModuleViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchModuleList() {
|
fun fetchModuleList() {
|
||||||
withContext(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
|
||||||
|
val oldModuleList = modules
|
||||||
|
|
||||||
val start = SystemClock.elapsedRealtime()
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
isOverlayAvailable = overlayFsAvailable()
|
||||||
|
|
||||||
val result = listModules()
|
val result = listModules()
|
||||||
|
|
||||||
@@ -76,6 +85,11 @@ class ModuleViewModel : ViewModel() {
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when both old and new is kotlin.collections.EmptyList
|
||||||
|
// moduleList update will don't trigger
|
||||||
|
if (oldModuleList === modules) {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user