Step 3: Added theme mode switching, introduced uninstall path manager and user-mode scanning toggle
This commit is contained in:
6
.github/workflows/build-manager.yml
vendored
6
.github/workflows/build-manager.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
|||||||
./randomizer
|
./randomizer
|
||||||
|
|
||||||
- name: Write key
|
- name: Write key
|
||||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/miuix' )) || github.ref_type == 'tag' }}
|
||||||
run: |
|
run: |
|
||||||
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
|
||||||
{
|
{
|
||||||
@@ -169,14 +169,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/miuix' )) || github.ref_type == 'tag' }}
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.determine.outputs.title }}
|
name: ${{ steps.determine.outputs.title }}
|
||||||
path: manager/app/build/outputs/apk/release/*.apk
|
path: manager/app/build/outputs/apk/release/*.apk
|
||||||
|
|
||||||
- name: Upload mappings
|
- name: Upload mappings
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }}
|
if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/miuix' )) || github.ref_type == 'tag' }}
|
||||||
with:
|
with:
|
||||||
name: "${{ steps.determine.outputs.title }}-mappings"
|
name: "${{ steps.determine.outputs.title }}-mappings"
|
||||||
path: "manager/app/build/outputs/mapping/release/"
|
path: "manager/app/build/outputs/mapping/release/"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,14 +15,4 @@ add_library(kernelsu
|
|||||||
|
|
||||||
find_library(log-lib log)
|
find_library(log-lib log)
|
||||||
|
|
||||||
if(ANDROID_ABI STREQUAL "arm64-v8a")
|
|
||||||
set(zakosign-lib ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libzakosign.so)
|
|
||||||
elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
|
|
||||||
set(zakosign-lib ${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a/libzakosign.so)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
|
|
||||||
target_link_libraries(kernelsu ${log-lib} ${zakosign-lib})
|
|
||||||
else()
|
|
||||||
target_link_libraries(kernelsu ${log-lib})
|
target_link_libraries(kernelsu ${log-lib})
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ NativeBridgeNP(getManagersList, jobject) {
|
|||||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||||
if (!modulePath) {
|
if (!modulePath) {
|
||||||
@@ -438,6 +438,7 @@ NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
||||||
return is_uid_scanner_enabled();
|
return is_uid_scanner_enabled();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "prelude.h"
|
#include "prelude.h"
|
||||||
#include "ksu.h"
|
#include "ksu.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||||
|
|
||||||
// Zako extern declarations
|
// Zako extern declarations
|
||||||
@@ -23,6 +24,7 @@ extern uint32_t zako_file_verify_esig(int fd, uint32_t flags);
|
|||||||
extern const char* zako_file_verrcidx2str(uint8_t index);
|
extern const char* zako_file_verrcidx2str(uint8_t index);
|
||||||
|
|
||||||
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
||||||
|
#endif
|
||||||
|
|
||||||
static int fd = -1;
|
static int fd = -1;
|
||||||
|
|
||||||
@@ -348,6 +350,7 @@ bool clear_uid_scanner_environment(void)
|
|||||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
bool verify_module_signature(const char* input) {
|
bool verify_module_signature(const char* input) {
|
||||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||||
if (input == NULL) {
|
if (input == NULL) {
|
||||||
@@ -404,3 +407,4 @@ bool verify_module_signature(const char* input) {
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ bool clear_dynamic_manager();
|
|||||||
|
|
||||||
bool get_managers_list(struct manager_list_info* info);
|
bool get_managers_list(struct manager_list_info* info);
|
||||||
|
|
||||||
|
#if 0
|
||||||
bool verify_module_signature(const char* input);
|
bool verify_module_signature(const char* input);
|
||||||
|
#endif
|
||||||
|
|
||||||
bool is_uid_scanner_enabled();
|
bool is_uid_scanner_enabled();
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ private fun StatusCard(
|
|||||||
text = workingText,
|
text = workingText,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = if (isSystemInDarkTheme()) Color(0xFFB8E6C5) else Color(0xFF1A5A2E)
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(2.dp))
|
Spacer(Modifier.height(2.dp))
|
||||||
Text(
|
Text(
|
||||||
@@ -378,6 +379,7 @@ private fun StatusCard(
|
|||||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (isSystemInDarkTheme()) Color(0xFF9DD4AC) else Color(0xFF2D7A4A)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.add
|
||||||
|
import androidx.compose.foundation.layout.displayCutout
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Palette
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import dev.chrisbanes.haze.HazeState
|
||||||
|
import dev.chrisbanes.haze.HazeStyle
|
||||||
|
import dev.chrisbanes.haze.HazeTint
|
||||||
|
import dev.chrisbanes.haze.hazeEffect
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||||
|
import top.yukonga.miuix.kmp.basic.Card
|
||||||
|
import top.yukonga.miuix.kmp.basic.Icon
|
||||||
|
import top.yukonga.miuix.kmp.basic.IconButton
|
||||||
|
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||||
|
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||||
|
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||||
|
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||||
|
import top.yukonga.miuix.kmp.icon.icons.useful.Back
|
||||||
|
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||||
|
import top.yukonga.miuix.kmp.utils.getWindowSize
|
||||||
|
import top.yukonga.miuix.kmp.utils.overScrollVertical
|
||||||
|
import top.yukonga.miuix.kmp.utils.scrollEndHaptic
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Destination<RootGraph>
|
||||||
|
fun Personalization(
|
||||||
|
navigator: DestinationsNavigator
|
||||||
|
) {
|
||||||
|
val scrollBehavior = MiuixScrollBehavior()
|
||||||
|
val hazeState = remember { HazeState() }
|
||||||
|
val hazeStyle = HazeStyle(
|
||||||
|
backgroundColor = colorScheme.background,
|
||||||
|
tint = HazeTint(colorScheme.background.copy(0.8f))
|
||||||
|
)
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
modifier = Modifier.hazeEffect(hazeState) {
|
||||||
|
style = hazeStyle
|
||||||
|
blurRadius = 30.dp
|
||||||
|
noiseFactor = 0f
|
||||||
|
},
|
||||||
|
color = Color.Transparent,
|
||||||
|
title = stringResource(R.string.personalization),
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
navigator.popBackStack()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = MiuixIcons.Useful.Back,
|
||||||
|
contentDescription = "Back"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
popupHost = { },
|
||||||
|
contentWindowInsets = WindowInsets.systemBars.add(WindowInsets.displayCutout).only(WindowInsetsSides.Horizontal)
|
||||||
|
) { innerPadding ->
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = context as? Activity
|
||||||
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(getWindowSize().height.dp)
|
||||||
|
.scrollEndHaptic()
|
||||||
|
.overScrollVertical()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
.hazeSource(state = hazeState)
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
contentPadding = innerPadding,
|
||||||
|
overscrollEffect = null,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
val themeItems = listOf(
|
||||||
|
stringResource(id = R.string.theme_follow_system),
|
||||||
|
stringResource(id = R.string.theme_light),
|
||||||
|
stringResource(id = R.string.theme_dark),
|
||||||
|
)
|
||||||
|
|
||||||
|
var themeMode by rememberSaveable {
|
||||||
|
mutableIntStateOf(prefs.getInt("theme_mode", 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
SuperDropdown(
|
||||||
|
title = stringResource(id = R.string.theme_mode),
|
||||||
|
summary = stringResource(id = R.string.theme_mode_summary),
|
||||||
|
items = themeItems,
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Palette,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = stringResource(id = R.string.theme_mode),
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectedIndex = themeMode,
|
||||||
|
onSelectedIndexChange = { index ->
|
||||||
|
prefs.edit {
|
||||||
|
putInt("theme_mode", index)
|
||||||
|
}
|
||||||
|
themeMode = index
|
||||||
|
activity?.recreate()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.layout.Column
|
import android.content.SharedPreferences
|
||||||
import androidx.compose.foundation.layout.Row
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
@@ -13,9 +18,11 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CleaningServices
|
||||||
|
import androidx.compose.material.icons.filled.Groups
|
||||||
|
import androidx.compose.material.icons.filled.Scanner
|
||||||
import androidx.compose.material.icons.rounded.Adb
|
import androidx.compose.material.icons.rounded.Adb
|
||||||
import androidx.compose.material.icons.rounded.BugReport
|
import androidx.compose.material.icons.rounded.BugReport
|
||||||
import androidx.compose.material.icons.rounded.ContactPage
|
import androidx.compose.material.icons.rounded.ContactPage
|
||||||
@@ -25,14 +32,14 @@ import androidx.compose.material.icons.rounded.DeveloperMode
|
|||||||
import androidx.compose.material.icons.rounded.EnhancedEncryption
|
import androidx.compose.material.icons.rounded.EnhancedEncryption
|
||||||
import androidx.compose.material.icons.rounded.Fence
|
import androidx.compose.material.icons.rounded.Fence
|
||||||
import androidx.compose.material.icons.rounded.FolderDelete
|
import androidx.compose.material.icons.rounded.FolderDelete
|
||||||
|
import androidx.compose.material.icons.rounded.Palette
|
||||||
import androidx.compose.material.icons.rounded.RemoveCircle
|
import androidx.compose.material.icons.rounded.RemoveCircle
|
||||||
import androidx.compose.material.icons.rounded.RemoveModerator
|
import androidx.compose.material.icons.rounded.RemoveModerator
|
||||||
import androidx.compose.material.icons.rounded.RestartAlt
|
import androidx.compose.material.icons.rounded.RestartAlt
|
||||||
import androidx.compose.material.icons.rounded.Security
|
|
||||||
import androidx.compose.material.icons.rounded.Update
|
import androidx.compose.material.icons.rounded.Update
|
||||||
import androidx.compose.material.icons.rounded.UploadFile
|
import androidx.compose.material.icons.rounded.UploadFile
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -54,6 +61,8 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
|||||||
import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.LogViewerDestination
|
import com.ramcosta.composedestinations.generated.destinations.LogViewerDestination
|
||||||
|
import com.ramcosta.composedestinations.generated.destinations.PersonalizationDestination
|
||||||
|
import com.ramcosta.composedestinations.generated.destinations.UmountManagerDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.HazeStyle
|
import dev.chrisbanes.haze.HazeStyle
|
||||||
@@ -62,27 +71,30 @@ import dev.chrisbanes.haze.hazeEffect
|
|||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||||
import com.sukisu.ultra.ui.component.DynamicManagerCard
|
import com.sukisu.ultra.ui.component.DynamicManagerCard
|
||||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
import com.sukisu.ultra.ui.component.SendLogDialog
|
import com.sukisu.ultra.ui.component.SendLogDialog
|
||||||
import com.sukisu.ultra.ui.component.SuperDropdown
|
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||||
import com.sukisu.ultra.ui.component.UninstallDialog
|
import com.sukisu.ultra.ui.component.UninstallDialog
|
||||||
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
||||||
|
import com.sukisu.ultra.ui.util.cleanRuntimeEnvironment
|
||||||
import com.sukisu.ultra.ui.util.execKsud
|
import com.sukisu.ultra.ui.util.execKsud
|
||||||
|
import com.sukisu.ultra.ui.util.getUidMultiUserScan
|
||||||
|
import com.sukisu.ultra.ui.util.readUidScannerFile
|
||||||
|
import com.sukisu.ultra.ui.util.setUidAutoScan
|
||||||
|
import com.sukisu.ultra.ui.util.setUidMultiUserScan
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
|
||||||
import top.yukonga.miuix.kmp.basic.Card
|
import top.yukonga.miuix.kmp.basic.Card
|
||||||
import top.yukonga.miuix.kmp.basic.Icon
|
import top.yukonga.miuix.kmp.basic.Icon
|
||||||
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||||
import top.yukonga.miuix.kmp.basic.Text
|
|
||||||
import top.yukonga.miuix.kmp.basic.TextButton
|
|
||||||
import top.yukonga.miuix.kmp.basic.TextField
|
|
||||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||||
import top.yukonga.miuix.kmp.extra.SuperArrow
|
import top.yukonga.miuix.kmp.extra.SuperArrow
|
||||||
import top.yukonga.miuix.kmp.extra.SuperDialog
|
|
||||||
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
||||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||||
import top.yukonga.miuix.kmp.utils.getWindowSize
|
import top.yukonga.miuix.kmp.utils.getWindowSize
|
||||||
@@ -148,6 +160,31 @@ fun SettingPager(
|
|||||||
mutableStateOf(prefs.getBoolean("check_update", true))
|
mutableStateOf(prefs.getBoolean("check_update", true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
val personalization = stringResource(id = R.string.personalization)
|
||||||
|
SuperArrow(
|
||||||
|
title = personalization,
|
||||||
|
summary = stringResource(id = R.string.personalization_summary),
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Palette,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = personalization,
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(PersonalizationDestination) {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 12.dp)
|
.padding(top = 12.dp)
|
||||||
@@ -493,6 +530,20 @@ fun SettingPager(
|
|||||||
DynamicManagerCard()
|
DynamicManagerCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KsuIsValid {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
UidScannerSection(prefs = prefs, scope = scope, context = context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KsuIsValid {
|
KsuIsValid {
|
||||||
val lkmMode = Natives.isLkmMode
|
val lkmMode = Natives.isLkmMode
|
||||||
if (lkmMode) {
|
if (lkmMode) {
|
||||||
@@ -544,6 +595,28 @@ fun SettingPager(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
val lkmMode = Natives.isLkmMode
|
||||||
|
if (lkmMode) {
|
||||||
|
val umontManager = stringResource(id = R.string.umount_path_manager)
|
||||||
|
SuperArrow(
|
||||||
|
title = umontManager,
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.FolderDelete,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = umontManager,
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
navigator.navigate(UmountManagerDestination) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SuperArrow(
|
SuperArrow(
|
||||||
title = stringResource(id = R.string.send_log),
|
title = stringResource(id = R.string.send_log),
|
||||||
leftAction = {
|
leftAction = {
|
||||||
@@ -602,3 +675,149 @@ enum class UninstallType(val icon: ImageVector, val title: Int, val message: Int
|
|||||||
NONE(Icons.Rounded.Adb, 0, 0)
|
NONE(Icons.Rounded.Adb, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UidScannerSection(
|
||||||
|
prefs: SharedPreferences,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
context: Context
|
||||||
|
) {
|
||||||
|
val realAuto = Natives.isUidScannerEnabled()
|
||||||
|
val realMulti = getUidMultiUserScan()
|
||||||
|
|
||||||
|
var autoOn by remember { mutableStateOf(realAuto) }
|
||||||
|
var multiOn by remember { mutableStateOf(realMulti) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
autoOn = realAuto
|
||||||
|
multiOn = realMulti
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean("uid_auto_scan", autoOn)
|
||||||
|
putBoolean("uid_multi_user_scan", multiOn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SuperSwitch(
|
||||||
|
title = stringResource(R.string.uid_auto_scan_title),
|
||||||
|
summary = stringResource(R.string.uid_auto_scan_summary),
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Scanner,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = stringResource(R.string.uid_auto_scan_title),
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
checked = autoOn,
|
||||||
|
onCheckedChange = { target ->
|
||||||
|
autoOn = target
|
||||||
|
if (!target) multiOn = false
|
||||||
|
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
setUidAutoScan(target)
|
||||||
|
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
autoOn = actual
|
||||||
|
if (!actual) multiOn = false
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean("uid_auto_scan", actual)
|
||||||
|
putBoolean("uid_multi_user_scan", multiOn)
|
||||||
|
}
|
||||||
|
if (actual != target) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.uid_scanner_setting_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = autoOn,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
SuperSwitch(
|
||||||
|
title = stringResource(R.string.uid_multi_user_scan_title),
|
||||||
|
summary = stringResource(R.string.uid_multi_user_scan_summary),
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Groups,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = stringResource(R.string.uid_multi_user_scan_title),
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
checked = multiOn,
|
||||||
|
onCheckedChange = { target ->
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val ok = setUidMultiUserScan(target)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (ok) {
|
||||||
|
multiOn = target
|
||||||
|
prefs.edit { putBoolean("uid_multi_user_scan", target) }
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.uid_scanner_setting_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = autoOn,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
val confirmDialog = rememberConfirmDialog()
|
||||||
|
SuperArrow(
|
||||||
|
title = stringResource(R.string.clean_runtime_environment),
|
||||||
|
summary = stringResource(R.string.clean_runtime_environment_summary),
|
||||||
|
leftAction = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.CleaningServices,
|
||||||
|
modifier = Modifier.padding(end = 16.dp),
|
||||||
|
contentDescription = stringResource(R.string.clean_runtime_environment),
|
||||||
|
tint = colorScheme.onBackground
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
if (confirmDialog.awaitConfirm(
|
||||||
|
title = context.getString(R.string.clean_runtime_environment),
|
||||||
|
content = context.getString(R.string.clean_runtime_environment_confirm)
|
||||||
|
) == ConfirmResult.Confirmed
|
||||||
|
) {
|
||||||
|
if (cleanRuntimeEnvironment()) {
|
||||||
|
autoOn = false
|
||||||
|
multiOn = false
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean("uid_auto_scan", false)
|
||||||
|
putBoolean("uid_multi_user_scan", false)
|
||||||
|
}
|
||||||
|
Natives.setUidScannerEnabled(false)
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.clean_runtime_environment_success),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.clean_runtime_environment_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,469 @@
|
|||||||
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
|
import androidx.compose.material.icons.outlined.Info
|
||||||
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
|
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||||
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import top.yukonga.miuix.kmp.basic.Button
|
||||||
|
import top.yukonga.miuix.kmp.basic.Card
|
||||||
|
import top.yukonga.miuix.kmp.basic.FloatingActionButton
|
||||||
|
import top.yukonga.miuix.kmp.basic.Icon
|
||||||
|
import top.yukonga.miuix.kmp.basic.IconButton
|
||||||
|
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||||
|
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||||
|
import top.yukonga.miuix.kmp.basic.Text
|
||||||
|
import top.yukonga.miuix.kmp.basic.TextButton
|
||||||
|
import top.yukonga.miuix.kmp.basic.TextField
|
||||||
|
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||||
|
import top.yukonga.miuix.kmp.extra.SuperDialog
|
||||||
|
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||||
|
import top.yukonga.miuix.kmp.icon.icons.useful.Back
|
||||||
|
import top.yukonga.miuix.kmp.icon.icons.useful.Delete
|
||||||
|
import top.yukonga.miuix.kmp.icon.icons.useful.Refresh
|
||||||
|
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||||
|
import top.yukonga.miuix.kmp.utils.getWindowSize
|
||||||
|
import top.yukonga.miuix.kmp.utils.overScrollVertical
|
||||||
|
import top.yukonga.miuix.kmp.utils.scrollEndHaptic
|
||||||
|
|
||||||
|
private val SPACING_SMALL = 3.dp
|
||||||
|
private val SPACING_MEDIUM = 8.dp
|
||||||
|
private val SPACING_LARGE = 16.dp
|
||||||
|
|
||||||
|
data class UmountPathEntry(
|
||||||
|
val path: String,
|
||||||
|
val flags: Int,
|
||||||
|
val isDefault: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
@Destination<RootGraph>
|
||||||
|
@Composable
|
||||||
|
fun UmountManager(navigator: DestinationsNavigator) {
|
||||||
|
val scrollBehavior = MiuixScrollBehavior()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val confirmDialog = rememberConfirmDialog()
|
||||||
|
|
||||||
|
var pathList by remember { mutableStateOf<List<UmountPathEntry>>(emptyList()) }
|
||||||
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
|
var showAddDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
fun loadPaths() {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
isLoading = true
|
||||||
|
val result = listUmountPaths()
|
||||||
|
val entries = parseUmountPaths(result)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
pathList = entries
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
loadPaths()
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.umount_path_manager),
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { navigator.navigateUp() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = MiuixIcons.Useful.Back,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { loadPaths() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = MiuixIcons.Useful.Refresh,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color = Color.Transparent,
|
||||||
|
scrollBehavior = scrollBehavior
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = { showAddDialog = true }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.height(getWindowSize().height.dp)
|
||||||
|
.scrollEndHaptic()
|
||||||
|
.overScrollVertical()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(SPACING_LARGE)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(SPACING_LARGE),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Info,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(SPACING_MEDIUM))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.umount_path_restart_notice)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
top.yukonga.miuix.kmp.basic.CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(horizontal = SPACING_LARGE, vertical = SPACING_MEDIUM),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
|
||||||
|
) {
|
||||||
|
items(pathList, key = { it.path }) { entry ->
|
||||||
|
UmountPathCard(
|
||||||
|
entry = entry,
|
||||||
|
onDelete = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val success = removeUmountPath(entry.path)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (success) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.umount_path_removed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
loadPaths()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.operation_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(SPACING_LARGE))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = SPACING_LARGE),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
if (confirmDialog.awaitConfirm(
|
||||||
|
title = context.getString(R.string.confirm_action),
|
||||||
|
content = context.getString(R.string.confirm_clear_custom_paths)
|
||||||
|
) == ConfirmResult.Confirmed) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val success = clearCustomUmountPaths()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (success) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.custom_paths_cleared),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
loadPaths()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.operation_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.clear_custom_paths))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val success = applyUmountConfigToKernel()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (success) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.config_applied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.operation_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.apply_config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showAddDialog) {
|
||||||
|
AddUmountPathDialog(
|
||||||
|
onDismiss = { showAddDialog = false },
|
||||||
|
onConfirm = { path, flags ->
|
||||||
|
showAddDialog = false
|
||||||
|
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val success = addUmountPath(path, flags)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (success) {
|
||||||
|
saveUmountConfig()
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.umount_path_added),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
loadPaths()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.operation_failed),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UmountPathCard(
|
||||||
|
entry: UmountPathEntry,
|
||||||
|
onDelete: () -> Unit
|
||||||
|
) {
|
||||||
|
val confirmDialog = rememberConfirmDialog()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(SPACING_LARGE),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Folder,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (entry.isDefault)
|
||||||
|
colorScheme.primary
|
||||||
|
else
|
||||||
|
colorScheme.secondary,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(SPACING_LARGE))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = entry.path
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||||
|
Text(
|
||||||
|
text = buildString {
|
||||||
|
append(context.getString(R.string.flags))
|
||||||
|
append(": ")
|
||||||
|
append(entry.flags.toUmountFlagName(context))
|
||||||
|
if (entry.isDefault) {
|
||||||
|
append(" | ")
|
||||||
|
append(context.getString(R.string.default_entry))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color = colorScheme.onSurfaceVariantSummary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.isDefault) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
if (confirmDialog.awaitConfirm(
|
||||||
|
title = context.getString(R.string.confirm_delete),
|
||||||
|
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
|
||||||
|
) == ConfirmResult.Confirmed) {
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = MiuixIcons.Useful.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AddUmountPathDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: (String, Int) -> Unit
|
||||||
|
) {
|
||||||
|
var path by rememberSaveable { mutableStateOf("") }
|
||||||
|
var flags by rememberSaveable { mutableStateOf("-1") }
|
||||||
|
val showDialog = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
SuperDialog(
|
||||||
|
show = showDialog,
|
||||||
|
title = stringResource(R.string.add_umount_path),
|
||||||
|
onDismissRequest = {
|
||||||
|
showDialog.value = false
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = path,
|
||||||
|
onValueChange = { path = it },
|
||||||
|
label = stringResource(R.string.mount_path),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = flags,
|
||||||
|
onValueChange = { flags = it },
|
||||||
|
label = stringResource(R.string.umount_flags),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.umount_flags_hint),
|
||||||
|
color = colorScheme.onSurfaceVariantSummary,
|
||||||
|
modifier = Modifier.padding(start = SPACING_MEDIUM)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(android.R.string.cancel),
|
||||||
|
onClick = {
|
||||||
|
showDialog.value = false
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(20.dp))
|
||||||
|
TextButton(
|
||||||
|
text = stringResource(android.R.string.ok),
|
||||||
|
onClick = {
|
||||||
|
val flagsInt = flags.toIntOrNull() ?: -1
|
||||||
|
showDialog.value = false
|
||||||
|
onConfirm(path, flagsInt)
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
enabled = path.isNotBlank()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseUmountPaths(output: String): List<UmountPathEntry> {
|
||||||
|
val lines = output.lines().filter { it.isNotBlank() }
|
||||||
|
if (lines.size < 2) return emptyList()
|
||||||
|
|
||||||
|
return lines.drop(2).mapNotNull { line ->
|
||||||
|
val parts = line.trim().split(Regex("\\s+"))
|
||||||
|
if (parts.size >= 3) {
|
||||||
|
UmountPathEntry(
|
||||||
|
path = parts[0],
|
||||||
|
flags = parts[1].toIntOrNull() ?: -1,
|
||||||
|
isDefault = parts[2].equals("Yes", ignoreCase = true)
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.toUmountFlagName(context: Context): String {
|
||||||
|
return when (this) {
|
||||||
|
-1 -> context.getString(R.string.mnt_detach)
|
||||||
|
else -> this.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.sukisu.ultra.ui.theme
|
package com.sukisu.ultra.ui.theme
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||||
import top.yukonga.miuix.kmp.theme.darkColorScheme
|
import top.yukonga.miuix.kmp.theme.darkColorScheme
|
||||||
import top.yukonga.miuix.kmp.theme.lightColorScheme
|
import top.yukonga.miuix.kmp.theme.lightColorScheme
|
||||||
@@ -11,8 +13,18 @@ fun KernelSUTheme(
|
|||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val themeMode = prefs.getInt("theme_mode", 0)
|
||||||
|
|
||||||
|
val useDarkTheme = when (themeMode) {
|
||||||
|
1 -> false // 浅色
|
||||||
|
2 -> true // 深色
|
||||||
|
else -> darkTheme // 跟随系统
|
||||||
|
}
|
||||||
|
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
darkTheme -> darkColorScheme()
|
useDarkTheme -> darkColorScheme()
|
||||||
else -> lightColorScheme()
|
else -> lightColorScheme()
|
||||||
}
|
}
|
||||||
MiuixTheme(
|
MiuixTheme(
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -206,4 +206,44 @@
|
|||||||
<string name="invalid_sign_config">无效的管理器配置</string>
|
<string name="invalid_sign_config">无效的管理器配置</string>
|
||||||
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string>
|
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string>
|
||||||
<string name="dynamic_manager_clear_failed">清空动态管理器配置失败</string>
|
<string name="dynamic_manager_clear_failed">清空动态管理器配置失败</string>
|
||||||
|
<string name="uid_auto_scan_title">用户态扫描应用列表</string>
|
||||||
|
<string name="uid_auto_scan_summary">开启后将使用用户态扫描应用列表,提高稳定性 (因内核扫描应用列表出现卡死等问题可以尝试打开此选项) </string>
|
||||||
|
<string name="uid_multi_user_scan_title">多用户应用扫描</string>
|
||||||
|
<string name="uid_multi_user_scan_summary">开启后将扫描所有用户的应用,包括工作资料等</string>
|
||||||
|
<string name="uid_scanner_setting_failed">设置失败,请检查权限</string>
|
||||||
|
<string name="clean_runtime_environment">清理运行环境</string>
|
||||||
|
<string name="clean_runtime_environment_summary">清理运行时文件并停止扫描服务</string>
|
||||||
|
<string name="clean_runtime_environment_confirm">您确定要清理运行环境吗?这将停止扫描服务并删除相关文件</string>
|
||||||
|
<string name="clean_runtime_environment_success">运行环境清理成功</string>
|
||||||
|
<string name="clean_runtime_environment_failed">运行环境清理失败</string>
|
||||||
|
<!-- umount Manager -->
|
||||||
|
<string name="umount_path_manager">Umount 路径管理</string>
|
||||||
|
<string name="umount_path_manager_summary">管理内核卸载路径</string>
|
||||||
|
<string name="umount_path_restart_notice">添加或删除路径后需要重启设备才能生效。系统会在下次启动时应用新的配置。</string>
|
||||||
|
<string name="add_umount_path">添加 Umount 路径</string>
|
||||||
|
<string name="mount_path">挂载路径</string>
|
||||||
|
<string name="umount_flags">卸载标志</string>
|
||||||
|
<string name="umount_flags_hint">0=正常卸载, 8=MNT_DETACH, -1=自动</string>
|
||||||
|
<string name="flags">标志</string>
|
||||||
|
<string name="default_entry">默认条目</string>
|
||||||
|
<string name="confirm_delete">确认删除</string>
|
||||||
|
<string name="confirm_delete_umount_path">确定要删除路径 %s 吗?</string>
|
||||||
|
<string name="umount_path_added">路径已添加,重启后生效</string>
|
||||||
|
<string name="umount_path_removed">路径已删除,重启后生效</string>
|
||||||
|
<string name="operation_failed">操作失败</string>
|
||||||
|
<string name="confirm_action">确认操作</string>
|
||||||
|
<string name="confirm_clear_custom_paths">确定要清除所有自定义路径吗?(默认路径将保留)</string>
|
||||||
|
<string name="custom_paths_cleared">自定义路径已清除</string>
|
||||||
|
<string name="clear_custom_paths">清除自定义</string>
|
||||||
|
<string name="apply_config">应用配置</string>
|
||||||
|
<string name="config_applied">配置已应用到内核</string>
|
||||||
|
<!-- Personalization Settings -->
|
||||||
|
<string name="personalization">个性化</string>
|
||||||
|
<string name="personalization_summary">自定义应用外观和主题</string>
|
||||||
|
<string name="theme_settings">主题设置</string>
|
||||||
|
<string name="theme_mode">主题模式</string>
|
||||||
|
<string name="theme_mode_summary">选择应用的显示主题</string>
|
||||||
|
<string name="theme_light">浅色</string>
|
||||||
|
<string name="theme_dark">深色</string>
|
||||||
|
<string name="theme_follow_system">跟随系统</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -208,4 +208,46 @@
|
|||||||
<string name="invalid_sign_config">Invalid Manager configuration</string>
|
<string name="invalid_sign_config">Invalid Manager configuration</string>
|
||||||
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string>
|
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string>
|
||||||
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string>
|
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string>
|
||||||
|
<!-- UID Scanner Settings -->
|
||||||
|
<string name="uid_auto_scan_title">User-mode scanning application list</string>
|
||||||
|
<string name="uid_auto_scan_summary">Enabling this option will use user-mode scanning for the application list, improving stability. (If you encounter issues such as freezing during kernel scanning of the application list, you may try enabling this option.)</string>
|
||||||
|
<string name="uid_multi_user_scan_title">Multi-User Application Scanning</string>
|
||||||
|
<string name="uid_multi_user_scan_summary">When enabled, scans applications for all users, including work profiles</string>
|
||||||
|
<string name="uid_scanner_setting_failed">Setting failed, please check permissions</string>
|
||||||
|
<string name="clean_runtime_environment">Clean Runtime Environment</string>
|
||||||
|
<string name="clean_runtime_environment_summary">Clean up runtime files and stop the scanner service</string>
|
||||||
|
<string name="clean_runtime_environment_confirm">Are you sure you want to clean the runtime environment? This will stop the scanner service and remove related files.</string>
|
||||||
|
<string name="clean_runtime_environment_success">Runtime environment cleaned successfully</string>
|
||||||
|
<string name="clean_runtime_environment_failed">Failed to clean runtime environment</string>
|
||||||
|
<!-- umount Manager -->
|
||||||
|
<string name="umount_path_manager">Umount Path Management</string>
|
||||||
|
<string name="umount_path_manager_summary">Manage kernel unmount paths</string>
|
||||||
|
<string name="umount_path_restart_notice">A reboot is required for changes to take effect. The system will apply the new configuration on the next boot.</string>
|
||||||
|
<string name="add_umount_path">Add Umount Path</string>
|
||||||
|
<string name="mount_path">Mount Path</string>
|
||||||
|
<string name="umount_flags">Unmount Flags</string>
|
||||||
|
<string name="umount_flags_hint">0=Normal unmount, 8=MNT_DETACH, -1=Auto</string>
|
||||||
|
<string name="flags">Flags</string>
|
||||||
|
<string name="default_entry">Default Entry</string>
|
||||||
|
<string name="confirm_delete">Confirm Delete</string>
|
||||||
|
<string name="confirm_delete_umount_path">Are you sure you want to delete the path %s?</string>
|
||||||
|
<string name="umount_path_added">Path added, will take effect after reboot</string>
|
||||||
|
<string name="umount_path_removed">Path removed, will take effect after reboot</string>
|
||||||
|
<string name="operation_failed">Operation failed</string>
|
||||||
|
<string name="confirm_action">Confirm Action</string>
|
||||||
|
<string name="confirm_clear_custom_paths">Are you sure you want to clear all custom paths? (Default paths will be preserved)</string>
|
||||||
|
<string name="custom_paths_cleared">Custom paths cleared</string>
|
||||||
|
<string name="clear_custom_paths">Clear Custom Paths</string>
|
||||||
|
<string name="apply_config">Apply Configuration</string>
|
||||||
|
<string name="config_applied">Configuration applied to kernel</string>
|
||||||
|
<string name="mnt_detach">MNT_DETACH</string>
|
||||||
|
<!-- Personalization Settings -->
|
||||||
|
<string name="personalization">Personalization</string>
|
||||||
|
<string name="personalization_summary">Customize the app\'s appearance and theme</string>
|
||||||
|
<string name="theme_settings">Theme Settings</string>
|
||||||
|
<string name="theme_mode">Theme Mode</string>
|
||||||
|
<string name="theme_mode_summary">Select the app\'s display theme</string>
|
||||||
|
<string name="theme_light">Light</string>
|
||||||
|
<string name="theme_dark">Dark</string>
|
||||||
|
<string name="theme_follow_system">Follow System</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user